Создании эффектов линзы, вращающихся цилиндра и сферы с рисунками на них

На главную страницу

Графические эффекты (Эффект линзы, вращающегося цилиндра, конуса и сферы с рисунками на них)

Аннотация: Когда в программу "Relaxation" - программа для расслабления зрения мне захотелось добавить несколько модных эффектов (скоро выйдет новая версия этой популярной программы, в которую они будут включены), то, как всегда, я прогуглил интернет.

Но ничего приемлемого для эффектов линзы, вращающихся цилиндра, конуса и сферы с рисунками на них я так и не нашёл. Единственный пример, найденный с описанием и кодом, алгоритм эффекта линзы, не мог быть переписанным на C# из-за ассемблерных вставок, все другие примеры грешили сложной математикой, но нужного эффекта после их реализации в коде не давали.

Что касается цилиндра, конуса и сферы, то кроме WPF реализации ничего в интернете нет.

Пришлось решать поставленные задачи самому. И, как всегда, когда что то начинаешь делать, то оказывается оно не так уж и сложно. А классические примеры из презентации WPF (вращающиеся цилиндр, конус и сфера с рисунками на них), вполне реализуемы и без WPF, с использованием GDI+.

Этому и посвящена данная статья, как просто можно реализовать старыми добрыми способами, достаточно сложные графические эффекты.


В начало

1. Эффект Линзы

Алгоритм линзы, в указанной в аннотации статье, трактуется так:
Если линза на экране - окружность (x0,y0,r0), то в точках (x, y), для которых (x-x0)2+(y-y0)2<=r02, рисуется точка, которая, если бы линзы не было, изображалась бы в точке (x0+(x-x0)*k1,y0+(y-y0)*k), где k=const1 / ([sqrt]((x-x0)2+(y-y0)2) + const2).

Как видно, предлагается рисовать изображение под линзой по точкам. Но и автор этого алгоритма понимает, что рисовать каждую точку из всей совокупности - это долго. И поэтому не зря он предлагает "заранее просчитать таблицу смещений".

Этот алгоритм не сложен. В принципе его можно дополнить, взяв все биты изображения в массив указателей на пиксели, используя небезопасный контекст(unsafe) и класс Marshal. Далее, все указанные математические преобразования, можно выполнить уже в данном массиве, после чего вернуть изменённые пиксели изображения на то место, которое они должны принимать.

Однако есть способ и проще - взять кусок рисунка, который надо увеличить, растяyть его методами GDI (функция DrawImage), обрезать по форме линзы и скопировать в место, над которым находится линза.

Именно применение этого метода показано на Рис.1.

Проект решения имеет на главной форме кон

Рис.1. Эффект линзы тролы PictureBox, Timer, OpenFileDialog, SaveFileDialog и MenuStrip. MenuStrip имеет пункты добавления и удаления рисунка и выбора рисунков. При старте первый рисунок, который вы видите выше, формируется из ресурсов программы и сохраняется в директории программы Image под именем 01start.jpg.

Все эффекты подцепляются как классы эффектов. Для взаимодействия с классом в центральной форме:

  • формируется класс эффекта clLens clLens1 = new clLens(this);

    private void timer1_Tick(object sender, EventArgs e)
    {
     if (fStart)
     {
      fStart = false; 
      //Эта функция читает имена файлов из директории Images
      vGetFiles();
      //Создаём класс эффекта
      clLens1 = new clLens(this);
      //Выполняем работу, связанную с изменением размера
      //Эту функцию также необходимо вызвать при смене картинки изображения
      //и изменением размера поля программы. Правда, в тестовой версии программы, 
      //размер формируется в функции  vNewSize() непосредственно
      clLens1.vNewSize();
      return;
     }
     //Выполнение алгоритма по таймеру функция LineTick()
     if (clLens1 != null)
     {
      clLens1.LineTick();
     }
    }
    
  • этот кусок кода необходим для определения места положения линзы:

    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
     viMoseX = e.X;
     viMoseY = e.Y;
    }
    
  • после формирования класса и при каждом изменении размера формы или исходного рисунка вызывается метод vNewSize();

  • после формирования общих значений данных, зависящих от размера, периодически по таймеру вызывается метод LineTick(), который выполняет изменения в рисунке, согласно заложенного алгоритма.

Код класса всех эффектов не сложен, в его понятии помогут встроенные коментарии.

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;

namespace Lens
{
 class clLens
 {
  //Ссылка на класс основной форме передаётся в класс эффекта для 
  //возможности использования параметров и переменных основной формы
   private Form1 myForm;

   //Все байты исходного рисунка последовательно через ссылки
   private byte[] bmpBytesSource;

   //Все байты исходного рисунка разбитые по массивам составляющих ARGB
   private byte[,] brgPixelsB;
   private byte[,] brgPixelsG;
   private byte[,] brgPixelsR;
   private byte[,] brgPixelsL;

   //Ссылки на байты сформированного изображения в линзе 
   private byte[] bmpBytes;
  
   //Временные параметры для самостоятельного движения линзы
   private DateTime dt1 = DateTime.Now;
   private DateTime dt2 = DateTime.Now;
   private TimeSpan ts = DateTime.Now - DateTime.Now;

   //Направление самостоятельного движения линзы
   private bool fKudaX = false;
   private bool fKudaY = false;

   //Для определения направлений движения
   public Random r = new Random(DateTime.Now.Millisecond);
   
   //Переменные хранения рисунков 
   private static Bitmap bmpStart = null;
   private Bitmap bitmapWork { get; set; }
   private Bitmap bmpTemp { get; set; }
   private Bitmap bmpTemp1 { get; set; }
   
   //Вспомогательная переменная 
   private bool fBlock = false;

   //Диаметр линзы
   private int viDiamLens = 0;

   //Размеры рисунка 
   private int viWidth = 0;
   private int viHeight = 0;

   //Рабочий класс Graphics
   public Graphics graph = null;       

   //Переменные координат точки линзы
   private int viPointX = 0;
   private int viPointY = 0;
   private int viPointHideX = 0;
   private int viPointHideY = 0;
   private int viPointWorkX = 0;
   private int viPointWorkY = 0;
   private int viPointWorkHideX = 0;
   private int viPointWorkHideY = 0;

   private ImageAttributes imageattributes = new ImageAttributes();

   //Вспомогательные переменные
   private int viI { get; set; }
   private int viJ { get; set; }

   //Конструктор 
   public clLens(Form1 form1)
   {
       myForm = form1;
   }

   //Установки при старте и смене размера окна программы  и рисунка 
   public void vNewSize()
   {
       if (fBlock) return;
       fBlock = true;

       //Получаем адрес рисунка, в классе основной форме 
       //все имена рисунков находятся в массиве  myForm.srgFiles
       string s = myForm.srgFiles[myForm.viImage];
       if (!File.Exists(s))
       {
           fBlock = false;
           return;
       }

       //Картинка из памяти 
       bmpTemp = new Bitmap(s);

       //Делаем размер формы под размер картинки 
       myForm.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
       myForm.Width = bmpTemp.Width + 10;
       myForm.Height = bmpTemp.Height + 62;
       myForm.Left = (myForm.screen.Width - myForm.Width) / 2;
       myForm.Top=(myForm.screen.Height- myForm.Height) / 2;

       //Размер pictureBox1 под размер картинки
       myForm.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
       myForm.pictureBox1.Dock = System.Windows.Forms.DockStyle.None;
       myForm.pictureBox1.Width = bmpTemp.Width;
       myForm.pictureBox1.Height = bmpTemp.Height;
       myForm.pictureBox1.Left = (myForm.Width - bmpTemp.Width) / 2;
       myForm.pictureBox1.Top = (myForm.Height - bmpTemp.Height) / 2;  
       viWidth = myForm.pictureBox1.Width;
       viHeight = myForm.pictureBox1.Height;

       //Освобождаем ссылку на выбранный файл рисунка  bmpTemp.Dispose();
       //Исходный рисунок перенесём в bmpStart
       bmpStart = new Bitmap(viWidth, viHeight);
       graph = Graphics.FromImage(bmpStart);
       graph.DrawImage(bmpTemp, new Rectangle(0, 0, viWidth, viHeight), 0, 0, 
                                      bmpTemp.Width, bmpTemp.Height, GraphicsUnit.Pixel);
       bitmapWork = new Bitmap(viWidth, viHeight);                
       bmpTemp.Dispose();

       //Забираем ссылки на байты изображения в массив 
       bmpBytesSource = new Byte[viWidth * viHeight * 4];
       BitmapData bmpTempDataStart = bmpStart.LockBits(new Rectangle(0, 0, 
                viWidth, viHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
       unsafe
      {
       try
       {
        Marshal.Copy(bmpTempDataStart.Scan0, bmpBytesSource, 0, 
                                                                 viWidth * viHeight * 4);

       }
       catch (Exception)
       {                     
       }
       finally
       {
        bmpStart.UnlockBits(bmpTempDataStart);
        bmpTempDataStart = null;
       }
      } 

      //Задаём размер линзы 
      viDiamLens = (viWidth >= viHeight) ? viHeight : viWidth;
      viDiamLens = viDiamLens / 4;
      while (viDiamLens % 2 != 0 && viDiamLens % 4 != 0)
      {
       viDiamLens++;
      }
      //Устанавливаем вспомогательные переменные 
      viPointX = 0;
      viPointY = 0;
      fKudaX = false;
      fKudaY = false;
      ts = DateTime.Now - DateTime.Now;
      myForm.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
      fBlock = false;
      return;           
   }

   //Действия выполнеемые по времени    
   public void LineTick()
   {            
    if (fBlock) return;            
    fBlock = true;           
    //Рисунок для изображения в линзе (bmpTemp используем повторно, он свободен)
    bmpTemp = new Bitmap(viDiamLens, viDiamLens);
    graph = Graphics.FromImage(bmpTemp);
    //Заливаем красным
    graph.Clear(Color.FromArgb(255, 0, 0));
    //Рисцем элипс(круг, в размер линзы), залитый зелёным цветом на красном фоне
    graph.FillEllipse(new SolidBrush(Color.FromArgb(0, 255, 0)), -1, -1, 
                                                                        viDiamLens, viDiamLens);
    graph.Dispose();
    //Берём ссылки на пиксели в bmpBytesRedGreen
    byte[] bmpBytesRedGreen = new Byte[bmpTemp.Width * bmpTemp.Height * 4];
    BitmapData bmpTempData = bmpTemp.LockBits(new Rectangle(0, 0, viDiamLens, viDiamLens), 
                                             ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    unsafe
    {
     int* pbmpTemptData = null;
     try
     {
      pbmpTemptData = (int*)bmpTempData.Scan0;//указатель на первый байт bmpStartData                    
      Marshal.Copy(bmpTempData.Scan0, bmpBytesRedGreen, 0, viDiamLens * viDiamLens * 4);
     }
     catch (Exception)
     {
     }
     finally
     {
      bmpTemp.UnlockBits(bmpTempData);
      bmpTempData = null;
      pbmpTemptData = null;
      bmpTemp.Dispose();
     }
    }
    //Этот кусок кода необходим для определения места расположения линзы
    //Определяем стоит ли линзе самостоятельно менять положение
    //если указатель мыши не двигался установленное время
    viPointX = myForm.viMoseX;
    viPointY = myForm.viMoseY;
    if (viPointX == viPointHideX && viPointY == viPointHideY)
    {
     dt2 = DateTime.Now;
     ts = dt2 - dt1;
     if (ts.TotalSeconds > 5)
     {
      if (fKudaX)
      {
       viPointWorkX = viPointWorkHideX + r.Next(1, 5);
      }
      else
      {
       viPointWorkX = viPointWorkHideX - r.Next(1, 5);
      }
      if (fKudaY)
      {
       viPointWorkY = viPointWorkHideY + r.Next(1, 5);
      }
      else
      {
       viPointWorkY = viPointWorkHideY - r.Next(1, 5);
      }
      viPointWorkHideX = viPointWorkX;
      viPointWorkHideY = viPointWorkY;
     }
    }
    else
    {
     dt1 = DateTime.Now;
     viPointHideX = viPointX;
     viPointHideY = viPointY;
     viPointWorkHideX = viPointX;
     viPointWorkHideY = viPointY;
     viPointWorkX = viPointX;
     viPointWorkY = viPointY;
    }
    if (viPointWorkX == 0 && viPointWorkY == 0)
    {
     viPointWorkX = viWidth / 2;
     viPointWorkY = viHeight / 2;
    }
    if (viPointWorkX - viDiamLens / 2 <= 0)
    {
      fKudaX = true;
      viPointWorkX = viDiamLens / 2;
    }
    if (viPointWorkX + viDiamLens / 2 >= viWidth)
    {
      fKudaX = false;
      viPointWorkX = viWidth - viDiamLens / 2;
    }
    if (viPointWorkY - viDiamLens / 2 <= 0)
    {
     fKudaY = true;
     viPointWorkY = viDiamLens / 2;
    }
    if (viPointWorkY + viDiamLens / 2 >= viHeight)
    {
     fKudaY = false;
      viPointWorkY = viHeight - viDiamLens / 2;
    }

    //Делаем увеличенную картинку под линзой
    bmpTemp1 = new Bitmap(2 * viDiamLens, 2 * viDiamLens);
    graph = Graphics.FromImage(bmpTemp1);
    graph.DrawImage(bmpStart, new Rectangle(0, 0, 2 * viDiamLens, 2 * viDiamLens), 
              viPointWorkX - viDiamLens/2, viPointWorkY - viDiamLens/2, viDiamLens, 
                                                     viDiamLens, GraphicsUnit.Pixel);
    graph.Dispose();
    //Копируем часть увеличенной картинки в картинку линзы 
    bmpTemp = new Bitmap(viDiamLens, viDiamLens);
    graph = Graphics.FromImage(bmpTemp);
    graph.DrawImage(bmpTemp1, new Rectangle(0, 0, viDiamLens, viDiamLens), 
             bmpTemp1.Width/2 - viDiamLens / 2, bmpTemp1.Height/2 - viDiamLens / 2, 
                                             viDiamLens, viDiamLens, GraphicsUnit.Pixel);
    graph.Dispose();

    //Берём с неё все пиксели увеличенной картинки    
    byte[] bmpBytesBig = new Byte[bmpTemp.Width * bmpTemp.Height * 4];
    bmpTempData = bmpTemp.LockBits(new Rectangle(0, 0, viDiamLens, viDiamLens), 
                                         ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    unsafe
    {
     try
     {
      Marshal.Copy(bmpTempData.Scan0, bmpBytesBig, 0, viDiamLens * viDiamLens * 4);
     }
     catch (Exception)
     {
     }
     finally
     {
      bmpTemp.UnlockBits(bmpTempData);
      bmpTempData = null;
      bmpTemp.Dispose();
     }
    }
    brgPixelsB = new byte[viWidth, viHeight];
    brgPixelsG = new byte[viWidth, viHeight];
    brgPixelsR = new byte[viWidth, viHeight];
    brgPixelsL = new byte[viWidth, viHeight];
    //Забираем в массивы все биты источника, рассортировав их по цветам 
    for (int i = 0; i < viWidth; i++)
    {
     for (int j = 0; j < viHeight; j++)
     {
      brgPixelsB[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4)];
      brgPixelsG[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 1];
      brgPixelsR[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 2];
      brgPixelsL[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 3];
     }
    }
    //Туда куда надо кладем из увеличенной, там где зелёный круг 
    for (int i = 0; i < viDiamLens; i++)
    {
     for (int j = 0; j < viDiamLens; j++)
     {
     if (bmpBytesRedGreen[i * 4 + (j * viDiamLens * 4) + 1] == 255)
     {
      brgPixelsB[viPointWorkX - viDiamLens/ 2 + i, j + viPointWorkY - viDiamLens / 2] = 
                                                   bmpBytesBig[i * 4 + (j * viDiamLens * 4)];
      brgPixelsG[viPointWorkX - viDiamLens / 2 + i, j + viPointWorkY - viDiamLens / 2] = 
                                               bmpBytesBig[i * 4 + (j * viDiamLens * 4) + 1];
      brgPixelsR[viPointWorkX - viDiamLens / 2 + i, j + viPointWorkY - viDiamLens / 2] = 
                                               bmpBytesBig[i * 4 + (j * viDiamLens * 4) + 2];
      brgPixelsL[viPointWorkX - viDiamLens / 2 + i, j + viPointWorkY - viDiamLens / 2] = 
                                               bmpBytesBig[i * 4 + (j * viDiamLens * 4) + 3];
     }
    }
    }
    //Возвращаем байты увеличенной чпсти  на место  в bitmapWork
    bmpBytes = new byte[viWidth * 4 * viHeight];
    for (int i = 0; i < viWidth; i++)
    {
     for (int j = 0; j < viHeight; j++)
     {
      bmpBytes[i * 4 + (j * viWidth) * 4] = brgPixelsB[i, j];
      bmpBytes[i * 4 + (j * viWidth * 4) + 1] = brgPixelsG[i, j];
      bmpBytes[i * 4 + (j * viWidth * 4) + 2] = brgPixelsR[i, j];
      bmpBytes[i * 4 + (j * viWidth * 4) + 3] = brgPixelsL[i, j];
     }
    }
    BitmapData bitmapData = bitmapWork.LockBits(new Rectangle(0, 0, 
                   viWidth, viHeight), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
    unsafe
    {
     Marshal.Copy(bmpBytes, 0, bitmapData.Scan0, bmpBytes.Length);
    }
    bitmapWork.UnlockBits(bitmapData);
    graph = Graphics.FromImage(bitmapWork);
    //Обрамляем линзу
    graph.DrawEllipse(new Pen(Color.Gray, 2), viPointWorkX - viDiamLens / 2, 
                                      viPointWorkY - viDiamLens / 2, viDiamLens, viDiamLens);
    graph.Dispose();
    //Перендаём в pictureBox1
    myForm.pictureBox1.Image = bitmapWork;
    fBlock = false;
    return;
   }
  }
}


В начало

2. Вращающийся цилиндр с рисунками на нём

Реализация алгоритма вращающегося цилиндра в приведённом примере выполнена по аналогии с предыдущим примером. в отдельном классе "class clCylinder".

Напомним, что проект решения имеет на главной форме контролы PictureBox, Timer, OpenFileDialog, SaveFileDialog и MenuStrip. MenuStrip имеет пункты добавления и удаления рисунка и выбора рисунков. При старте первый рисунок, который вы видите выше, формируется из ресурсов программы и сохраняется в директории программы Image под именем 01start.jpg.

В коде центральной формы содаётся класс "clcylinder", который объявлен как:

private clCylinder clcylinder = null;

Сразу после созданя вызывается уже известная нам функция класса "vNewSize()", задача которой выполнить подготовительную работу. Основная задача этой работы забрать все байты рисунка в массив, распределить их в массивы по цветам. Кроме того определить насколько потребуется сдвинуть по форме элипса пиксели исходного рисунка для переднего плана вниз, а для заднего плана вверх.

clcylinder = new clCylinder(this);
clcylinder.vNewSize();
clcylinder = new clCylinder(this);        

Остальные пояснения представлены непосредственно в тексте приведённого кода. Результат показан на Рис.2.

Рис.2. Вращающийся цилиндр //Исходный код класса

       
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Cylinder
{
class clCylinder
{
private byte[,] brgPixelsB;
private byte[,] brgPixelsG;
private byte[,] brgPixelsR;
private byte[,] brgPixelsL;
private int viI;
private int viJ;
private double radB;
private double radA;
private int radB1;
private Form1 myForm;
private bool fBlock = false;
private Bitmap bmpTemp { get; set; }
private int viWidth = 0;
private int viHeight = 0;
public Graphics graphFore = null;
public Graphics graphBack = null;
public Graphics graph = null;
private byte[] bmpBytesSource;
private byte[] bmpBytesFore;
private byte[] bmpBytesBack;
//Исходная в половину 
private static Bitmap bmpStart = null;
private Bitmap bitmapWorkFore { get; set; }
private Bitmap bitmapWorkBack { get; set; }
private Bitmap bitmapWork { get; set; }
private Point[] ptEllipceFore;
private Point[] ptEllipceBack;        
public clCylinder(Form1 form1)
{           
    myForm = form1;
}
internal void vNewSize()
{
    if (fBlock) return;
    fBlock = true;
    //Берём дорогу картинки 
    string s = myForm.srgFiles[myForm.viImage];
    //Картинка из памяти помещаем в bmpTemp
    bmpTemp = new Bitmap(s);
    viWidth = bmpTemp.Width;
    viHeight = bmpTemp.Height;
    while (viWidth % 2 != 0)
    {
        viWidth--;
    }

    if (viWidth < 20 || viHeight < 20)
    {
        bmpTemp.Dispose();
        return;
    }
    bmpStart = new Bitmap(viWidth, viHeight);
    graphFore = Graphics.FromImage(bmpStart);
    graphFore.DrawImage(bmpTemp, new Rectangle(0, 0, viWidth, viHeight), 0, 0, 
                                                    bmpTemp.Width, bmpTemp.Height, GraphicsUnit.Pixel);
    //Освобождаем картинку в памяти 
    bmpTemp.Dispose();
    graphFore.Dispose();
    //Готовим массив ссылок на байты ARJB картинки
    bmpBytesSource = new Byte[viWidth * viHeight * 4];
    BitmapData bmpTempDataStart = bmpStart.LockBits(new Rectangle(0, 0, viWidth, viHeight), 
                                                     ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
    unsafe
    {
     try
     {
       //Забираем все ссылки на байты картинки 
       Marshal.Copy(bmpTempDataStart.Scan0, bmpBytesSource, 0, viWidth * viHeight * 4);
     }
     catch (Exception)
     {
     }
     finally
     {
      bmpStart.UnlockBits(bmpTempDataStart);
      bmpTempDataStart = null;
     }
    }
    //Распределяем ссылки в массивы пикселей по цветам 
    brgPixelsB = new byte[viWidth, viHeight];
    brgPixelsG = new byte[viWidth, viHeight];
    brgPixelsR = new byte[viWidth, viHeight];
    brgPixelsL = new byte[viWidth, viHeight];
    //Забираем в массивы все биты источника в массивы по цветам 
    for (int i = 0; i < viWidth; i++)
    {
        for (int j = 0; j < viHeight; j++)
        {
            brgPixelsB[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4)];
            brgPixelsG[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 1];
            brgPixelsR[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 2];
            brgPixelsL[i, j] = bmpBytesSource[i * 4 + (j * viWidth * 4) + 3];

        }
    }
    //Задаём радиусы элипсов для отрисовки верхней и нижней части вращающегося цилиндра
    //Крышка и донышко 
    radB = bmpStart.Width / 2 / 20;            
    radA = Math.Sqrt(((double)bmpStart.Width * (double)bmpStart.Width 
                                      / (2 * (Math.PI * Math.PI)) - radB * radB));            
    radB1 = (int)radB;
    //Прямоугольник залит синим цветом, внутри нарисован элипс краснам цветом
    //По красному будем определять границы в которых нужно брать пиксели 
    Bitmap bmp = new Bitmap(2 * (int)radA, 2 * radB1);            
    graphFore = Graphics.FromImage(bmp);
    graphFore.Clear(Color.Blue);
    graphFore.DrawEllipse(new Pen(Color.FromArgb(255, 0, 0), 1), 0, 0, 2 * 
                                                           (int)radA - 1, 2 * radB1 - 1);            
    Color cl;
    List listIb = new List();
    List listJb = new List();
    List listIf = new List();
    List listJf = new List();
    //Выбираем красные точки и их координаты помещаем 
    //в компоненты List соответственно для передней и задней частей элипса
    for (int i = 0; i < bmp.Width; i++)
    {
     for (int j = 0; j < bmp.Height; j++)
     {
      cl = bmp.GetPixel(i, j);
      if (cl.R == 255)
      {
       if (j < radB1)
       {
        //Координаты задней части
        listIb.Add(i);
        listJb.Add(j);
       }
       else
       {
        //Координаты передней части
        listIf.Add(i);
        listJf.Add(j);
       }

      }

     }
    }
    //Точки искривления для крышки и донышка передней и задней части
    //Идея, насколько надо сдвинуть каждую точку передние вниз,
    //задние поднять - тоесть изогнуть картинку по элипсу 
    ptEllipceFore = new Point[listIf.Count];
    for (int i = 0; i < listIf.Count; i++)
    {
        ptEllipceFore[i].X = listIf[i];
        ptEllipceFore[i].Y = -(listJf[i] - (int)radB);
    }
    ptEllipceBack = new Point[listIb.Count];
    for (int i = listIb.Count - 1; i >= 0; i--)
    {
     try
     {
      ptEllipceBack[i].X = listIb[i];
      ptEllipceBack[i].Y = (int)radB - listJb[i];
     }
     catch (Exception)
     {
    }
    }
    //Создаём классы для рисования на передней  bitmapWorkFore - graphFore, 
    //bitmapWorkBack - graphBack и на итоговом рисунке bitmapWork - graph            
    bitmapWorkFore = new Bitmap(2 * (int)radA, viHeight + 2 * radB1);
    bitmapWorkBack = new Bitmap(2 * (int)radA, viHeight + 2 * radB1);
    bitmapWork = new Bitmap(2 * (int)radA, viHeight + 2 * radB1);
    graphFore = Graphics.FromImage(bitmapWorkFore);
    graphBack = Graphics.FromImage(bitmapWorkBack);
    graph = Graphics.FromImage(bitmapWork);
    
    //Параметры приложения 
    myForm.WindowState = System.Windows.Forms.FormWindowState.Normal;
    myForm.Width = bmpStart.Width;
    myForm.Height = bmpStart.Height+200;
    myForm.Left = (myForm.screen.Width - myForm.Width) / 2;
    myForm.Top = (myForm.screen.Height - myForm.Height) / 2;
    myForm.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;           
    myForm.pictureBox1.BackColor = myForm.backColor;
    viI = 0;
    viJ = 0;
    fBlock = false;
}

internal void LineTick()
{
    if (fBlock) return;
    fBlock = true;
    graphFore.Clear(myForm.backColor);          
    int W = 2 * (int)radA;
    //Массивы пикселей для передней и задней части рисунка
    bmpBytesFore = new byte[W * 4 * (viHeight + 2 * radB1)];
    bmpBytesBack = new byte[W * 4 * (viHeight + 2 * radB1)];
    int viFrom1 = 0;
    int viTo1 = 0;
    //Забираем пиксели в массивы bmpBytesBack для части рисунка на заднем плане
    //Вращение определяет viJ, которое в конце функции  увеличивается (viJ++) 
    for (int i = W - 1; i >= 0; i--)
    {
     if (viWidth - 1 - i - viJ < 0)
     {
       viJ = 0;
     }                
     for (int j = viHeight - 1 + radB1; j >= -radB1; j--)
     {
       try
       {
           viTo1 = (W-i) * 4 + ((j + radB1) * W) * 4;
           //Учёт кривизны
           viFrom1 = j + ptEllipceBack[i].Y;
           if (viFrom1 < 0)
           {
               continue;
           }
           if (viFrom1 >= bitmapWorkBack.Height - 2 * radB1)
           {
               continue;
           }

           bmpBytesBack[viTo1] = brgPixelsB[viWidth - 1 - i - viJ, viFrom1];
           bmpBytesBack[viTo1 + 1] = brgPixelsG[viWidth - 1 - i - viJ, viFrom1];
           bmpBytesBack[viTo1 + 2] = brgPixelsR[viWidth - 1 - i - viJ, viFrom1];
           bmpBytesBack[viTo1 + 3] = brgPixelsL[viWidth - 1 - i - viJ, viFrom1];
       }
       catch (Exception)
       {                       
       }                 
     }
    }
    //Забираем пиксели в массивы bmpBytesBack для части рисунка на заднем плане
    //Вращение определяет viI, которое в конце функции  увеличивается (viJ++) 
    for (int i = 0; i < W; i++)
    {
     for (int j = 0; j < viHeight + radB1; j++)
     {
      try
      {
       viTo1 = i * 4 + ((j + radB1) * W) * 4;
       //Учёт кривизны
       viFrom1 = j + ptEllipceFore[i].Y;
       if (viFrom1 < 0)
       {
         continue;
       }
       if (viFrom1 >= bitmapWorkFore.Height - 2 * radB1)
       {
        continue;
       }
       bmpBytesFore[viTo1] = brgPixelsB[(i + viI) % viWidth, viFrom1];
       bmpBytesFore[viTo1 + 1] = brgPixelsG[(i + viI) % viWidth, viFrom1];
       bmpBytesFore[viTo1 + 2] = brgPixelsR[(i + viI) % viWidth, viFrom1];
       bmpBytesFore[viTo1 + 3] = brgPixelsL[(i + viI) % viWidth, viFrom1];
      }
      catch (Exception)
      {
            
      }
     }
    }

    //Перенос необходимых байтов в bitmapWorkBack 
    BitmapData bitmapData = bitmapWorkBack.LockBits(new Rectangle(0, 0, 
                     bitmapWorkBack.Width, bitmapWorkBack.Height), 
                        ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
    unsafe
    {
     Marshal.Copy(bmpBytesBack, 0, bitmapData.Scan0, bmpBytesFore.Length);

    }
    bitmapWorkBack.UnlockBits(bitmapData);
    //Перенос необходимых байтов в bitmapWorkFore 
    bitmapData = bitmapWorkFore.LockBits(new Rectangle(0, 0, 
                                bitmapWorkFore.Width, bitmapWorkFore.Height), 
                                  ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
    unsafe
    {
     Marshal.Copy(bmpBytesFore, 0, bitmapData.Scan0, bmpBytesFore.Length);

    }
    bitmapWorkFore.UnlockBits(bitmapData);
    //Перед отрисовкой определяем пиксели прозрачности 
    Color transparentcolor = bitmapWorkFore.GetPixel(0, 0);
    ImageAttributes imageattributes = new ImageAttributes();
    imageattributes.SetColorKey(transparentcolor, transparentcolor);
    //Отрисовка задней части рисунка bitmapWorkBack на bitmapWork
    graph.DrawImage(bitmapWorkBack, new 
                   Rectangle(0, 0, bitmapWorkBack.Width, bitmapWorkBack.Height),
                     0, 0, bitmapWorkBack.Width, bitmapWorkBack.Height, 
                                   GraphicsUnit.Pixel, imageattributes);
    //Отрисовка передней части bitmapWorkFore поверх задней части на  bitmapWork 
    graph.DrawImage(bitmapWorkFore, new Rectangle(0, 0, bitmapWorkBack.Width, 
                                                                 bitmapWorkFore.Height),
           0, 0, bitmapWorkFore.Width, bitmapWorkFore.Height, GraphicsUnit.Pixel, 
                                                                     imageattributes);
    myForm.pictureBox1.BackColor = bitmapWorkFore.GetPixel(0, 0);            
    //Публикация рисунка 
    myForm.pictureBox1.Image = bitmapWork;            
    viI++;
    viJ++;
    if (viI >= 1000 * viWidth) viI = 0;
    fBlock = false;
   }
  }
}


В начало

3. Вращающаяся сфера (шар) с рисунками на нёй

В создание эффекта заложены аналогичные принципы, что и при создании эффекта цилиндра (Рис.3.).

Рис.3. Вращающаяся сфера


В начало

4. Вращающийся конус с рисунками на нём

В создание эффекта заложены аналогичные принципы, что и при создании эффекта цилиндра (Рис.4.).

Рис.4. Вращающийся конус


В начало

5. Скачать исходные коды

Эффект Линзы ~ 2030 кб.

Вращающийся цилиндр с рисунком на нём ~ 2810 кб.

Вращающийся кунус с рисунком на нём ~ 2810 кб.

Вращающаяся сфера с рисунком на ней ~ 3560 кб.

Молчанов Владислав 30.09.2015г.

На главную страницу сайта автора

logo.gif

В начало раздела и книге автора по C#