Blog. Just Blog

Эффект витражного стекла на PHP

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Web-мастеру и не только | Автор: ManHunter
Эффект витражного стекла на PHP
Эффект витражного стекла на PHP

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

Парой строчек кода тут не ограничиться, преобразование выполняется в несколько шагов. Некоторые части алгоритма рекомендую взять на заметку, они вам могут пригодиться в дальнейшем.

Оригинальное изображение
Оригинальное изображение

Сперва надо загрузить изображение и "набросать" на него в случайном порядке точки, которые будут являться вершинами треугольников. Но не просто так, а во-первых, с заданной плотностью, и во-вторых, с учетом близости к границам изображения. Нужные значения вы можете подобрать опытным путем.
  1. $border_density=700;
  2. $image_density=100;
  3. $min_distance=60;
  4.  
  5. $im=imagecreatefromjpeg('image.jpg');
  6.  
  7. $width=imagesx($im);
  8. $height=imagesy($im);
  9.  
  10. $p=array();
  11.  
  12. //--------------------------------------------------------------
  13. // Накидывание на изображение точек с заданной плотностью
  14. //--------------------------------------------------------------
  15. for ($y=0$y<$height$y++) {
  16.     for ($x=0$x<$width$x++) {
  17.         $density=$image_density;
  18.  
  19.         if ($x==|| $y==|| $x==($width-1) || ($y==$height-1)) {
  20.             $density=$border_density;
  21.         }
  22.  
  23.         if (($x==&& $y==0) || ($x==&& $y==$height-1) ||
  24.             ($x==($width-1) && $y==0) ||
  25.             ($x==($width-1) && $y==($height-1)))
  26.         {
  27.             $density=$border_density;
  28.         }
  29.  
  30.         if ($density!=$border_density &&
  31.             ($x<$min_distance/|| $y<$min_distance/||
  32.             $x>($width-$min_distance/2) ||
  33.             $y>($height-$min_distance/2)))
  34.         {
  35.             continue;
  36.         }
  37.  
  38.         if (mt_rand(0,10000)<=$density) {
  39.             $p[]=array(
  40.                 'x'=>$x,
  41.                 'y'=>$y,
  42.             );
  43.         }
  44.     }
  45. }
Следующим шагом надо очистить изображение от точек, которые расположены слишком близко. В принципе, это делать не обязательно, но для достижения более красивого эффекта и более равномерного заполнения элементов витража лучше все-таки сделать. Также тут принудительно проставляются точки в каждый угол изображения, это тоже необходимо сделать.
  1. //--------------------------------------------------------------
  2. // Очистка близко расположенных точек
  3. //--------------------------------------------------------------
  4. $fixed=array(
  5.     array(
  6.         'x'=>0,
  7.         'y'=>0,
  8.     ),
  9.     array(
  10.         'x'=>($width-1),
  11.         'y'=>0,
  12.     ),
  13.     array(
  14.         'x'=>($width-1),
  15.         'y'=>($height-1),
  16.     ),
  17.     array(
  18.         'x'=>0,
  19.         'y'=>($height-1),
  20.     ),
  21. );
  22.  
  23. for($i=0$i<count($p); $i++) {
  24.     $pt=$p[$i];
  25.     if (($pt['x']==&& $pt['y']==0) ||
  26.         ($pt['x']==($width-1) && $pt['y']==0) ||
  27.         ($pt['x']==&& $pt['y']==($height-1)) ||
  28.         ($pt['x']==($width-1) && $pt['y']==($height-1)))
  29.     {
  30.         continue;
  31.     }
  32.  
  33.     $too_close=false;
  34.  
  35.     for ($j=0$j<count($fixed); $j++) {
  36.         $vt=$fixed[$j];
  37.         $dX $vt['x']-$pt['x'];
  38.         $dY $vt['y']-$pt['y'];
  39.         $dist sqrt($dX*$dX $dY*$dY);
  40.         if ($dist<$min_distance) {
  41.             $too_close=true;
  42.             break;
  43.         }
  44.     }
  45.     if (!$too_close) {
  46.         $fixed[]=$pt;
  47.     }
  48. }
  49. $p=$fixed;
Дальше интересный код - триангуляция Делоне. Массив точек надо преобразовать в группы по три точки, которые будут вершинами треугольников. Вложенные циклы сводят к нулю любые попытки оптимизации, но без них, к сожалению, не обойтись.
  1. //--------------------------------------------------------------
  2. // 2D-триангуляция Делоне
  3. //--------------------------------------------------------------
  4. $tri=array();
  5.  
  6. $z=array();
  7. for ($i=0$i<count($p); $i++) {
  8.     $z[$i]=$p[$i]['x']*$p[$i]['x'] + $p[$i]['y']*$p[$i]['y'];
  9. }
  10.  
  11. for ($i=0$i<(count($p)-2); $i++) {
  12.     for ($j=($i+1); $j<count($p); $j++) {
  13.         for ($k=($i+1); $k<count($p); $k++) {
  14.             if ($j!=$k) {
  15.                 $xn=($p[$j]['y']-$p[$i]['y']) * ($z[$k]-$z[$i]) -
  16.                     ($p[$k]['y']-$p[$i]['y']) * ($z[$j]-$z[$i]);
  17.                 $yn=($p[$k]['x']-$p[$i]['x']) * ($z[$j]-$z[$i]) -
  18.                     ($p[$j]['x']-$p[$i]['x']) * ($z[$k]-$z[$i]);
  19.                 $zn=($p[$j]['x']-$p[$i]['x']) * ($p[$k]['y']-$p[$i]['y']) -
  20.                     ($p[$k]['x']-$p[$i]['x']) * ($p[$j]['y']-$p[$i]['y']);
  21.  
  22.                 $flag=($zn<0);
  23.  
  24.                 if ($flag) {
  25.                     for ($m=0$m<count($p); $m++) {
  26.                         $flag=$flag && ((
  27.                             ($p[$m]['x']-$p[$i]['x']) * $xn +
  28.                             ($p[$m]['y']-$p[$i]['y']) * $yn +
  29.                             ($z[$m]-$z[$i]) * $zn) <=0
  30.                         );
  31.                     }
  32.                 }
  33.  
  34.                 if ($flag) {
  35.                     $tri[]=array($i$j$k);
  36.                 }
  37.             }
  38.         }
  39.     }
  40. }
После этого действия исходное изображение будет разделено примерно на такие треугольные фрагменты.

Изображение с нанесенной сеткой
Изображение с нанесенной сеткой

Нам останется только посчитать основной цвет в каждом треугольнике и затем выполнить заливку. В отличие от квадратных пикселей, тут придется проверять каждую точку изображения на предмет принадлежности тому или иному треугольнику. Снова тяжеленные вложенные циклы, снова потеря времени. Но увы, ничего не поделаешь. Небольшая оптимизация может быть достигнута за счет сохранения информации, какому треугольнику какая точка принадлежит, чтобы не тратить на это время при заливке. Тут используется вспомогательная функция, которой на вход подаются координаты проверяемой точки и координаты вершин треугольника, а на выходе она возвращает результат, принадлежит ли эта точка треугольнику или нет.
  1. //--------------------------------------------------------------
  2. // Проверить принадлежность точек треугольникам
  3. //--------------------------------------------------------------
  4. function point_in_triangle($pt$p1$p2$p3) {
  5.     $side_1=($pt['x']-$p2['x']) * ($p1['y']-$p2['y']) -
  6.         ($p1['x']-$p2['x']) * ($pt['y']-$p2['y']);
  7.     $side_2=($pt['x']-$p3['x']) * ($p2['y']-$p3['y']) -
  8.         ($p2['x']-$p3['x']) * ($pt['y']-$p3['y']);
  9.     $side_3=($pt['x']-$p1['x']) * ($p3['y']-$p1['y']) -
  10.         ($p3['x']-$p1['x']) * ($pt['y']-$p1['y']);
  11.  
  12.     return (($side_1<0.0 && $side_2<0.0 && $side_3<0.0) ||
  13.         ($side_1>=0.0 && $side_2>=0.0 && $side_3>=0.0));
  14. }
  15.  
  16. $color_data=array();
  17. $pixel_data=array();
  18.  
  19. for ($y=0$y<$height$y++) {
  20.     for ($x=0$x<$width$x++) {
  21.         $RGB=ImageColorAt($im$x$y);
  22.  
  23.         $R=($RGB >> 16) & 0xFF;
  24.         $G=($RGB >> 8) & 0xFF;
  25.         $B=$RGB 0xFF;
  26.  
  27.         for ($i=0$i<count($tri); $i++) {
  28.             if (!isset($color_data[$i])) {
  29.                 $color_data[$i]=array(
  30.                     'R'=>0,
  31.                     'G'=>0,
  32.                     'B'=>0,
  33.                     'count'=>0,
  34.                 );
  35.             }
  36.  
  37.             if (point_in_triangle(
  38.                 array('x'=>$x'y'=>$y),
  39.                 array('x'=>$p[$tri[$i][0]]['x'], 'y'=>$p[$tri[$i][0]]['y']),
  40.                 array('x'=>$p[$tri[$i][1]]['x'], 'y'=>$p[$tri[$i][1]]['y']),
  41.                 array('x'=>$p[$tri[$i][2]]['x'], 'y'=>$p[$tri[$i][2]]['y']))
  42.             ) {
  43.                 $color_data[$i]['R']+=$R;
  44.                 $color_data[$i]['G']+=$G;
  45.                 $color_data[$i]['B']+=$B;
  46.                 $color_data[$i]['count']++;
  47.  
  48.                 $pixel_data[$x][$y]=$i;
  49.                 break;
  50.             }
  51.         }
  52.     }
  53. }
  54.  
  55. //--------------------------------------------------------------
  56. // Высчитать средний цвет в каждом треугольнике
  57. //--------------------------------------------------------------
  58. for ($i=0$i<count($tri); $i++) {
  59.     $color_data[$i]['R']/=$color_data[$i]['count'];
  60.     $color_data[$i]['G']/=$color_data[$i]['count'];
  61.     $color_data[$i]['B']/=$color_data[$i]['count'];
  62. }
  63.  
  64. //--------------------------------------------------------------
  65. // Закрасить точки в каждом треугольнике
  66. //--------------------------------------------------------------
  67. for ($y=0$y<$height$y++) {
  68.     for ($x=0$x<$width$x++) {
  69.         $color=ImageColorAllocate($im,
  70.             $color_data[$pixel_data[$x][$y]]['R'],
  71.             $color_data[$pixel_data[$x][$y]]['G'],
  72.             $color_data[$pixel_data[$x][$y]]['B']
  73.         );
  74.         imagesetpixel($im,$x,$y,$color);
  75.     }
  76. }
Изображение почти готово, остался последний шаг. Рисуем сетку витража поверх картинки. Можно обойтись и без нее, но так будет красивее. Тем более, что этот шаг практически не отнимает времени.
  1. //--------------------------------------------------------------
  2. // Нарисовать сетку витража
  3. //--------------------------------------------------------------
  4. $white=ImageColorAllocate($im255255255);
  5.  
  6. foreach($tri as $tr) {
  7.     imagepolygon($im, array(
  8.         $p[$tr[0]]['x'], $p[$tr[0]]['y'],
  9.         $p[$tr[1]]['x'], $p[$tr[1]]['y'],
  10.         $p[$tr[2]]['x'], $p[$tr[2]]['y'],
  11.     ), 3$white);
  12. }
Вот и все, теперь готовое изображение можно сохранить в файл или вывести в браузер. На всякий случай еще раз предупреждаю, что процесс наложения эффекта требует кучу времени и памяти, про преобразования в реальном времени можете забыть сразу.

Поделиться ссылкой ВКонтакте
Просмотров: 559 | Комментариев: 2

Метки: PHP, графика
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (16.02.2022 в 13:15):
Так это ж она и есть.
Grey (16.02.2022 в 08:07):
Напомнило триангуляцию

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.09 сек. / MySQL: 2 (0.0079 сек.) / Память: 4.5 Mb
Наверх