Эффект витражного стекла на PHP
Эффект витражного стекла на PHP
Очень красивый графический эффект, по своему принципу напоминающий пикселизацию, только тут используются треугольные фрагменты случайного размера, расположенные по всей площади изображения.
Парой строчек кода тут не ограничиться, преобразование выполняется в несколько шагов. Некоторые части алгоритма рекомендую взять на заметку, они вам могут пригодиться в дальнейшем.
Оригинальное изображение
Сперва надо загрузить изображение и "набросать" на него в случайном порядке точки, которые будут являться вершинами треугольников. Но не просто так, а во-первых, с заданной плотностью, и во-вторых, с учетом близости к границам изображения. Нужные значения вы можете подобрать опытным путем.
Code (PHP) : Убрать нумерацию
- $border_density=700;
- $image_density=100;
- $min_distance=60;
- $im=imagecreatefromjpeg('image.jpg');
- $width=imagesx($im);
- $height=imagesy($im);
- $p=array();
- //--------------------------------------------------------------
- // Накидывание на изображение точек с заданной плотностью
- //--------------------------------------------------------------
- for ($y=0; $y<$height; $y++) {
- for ($x=0; $x<$width; $x++) {
- $density=$image_density;
- if ($x==0 || $y==0 || $x==($width-1) || ($y==$height-1)) {
- $density=$border_density;
- }
- if (($x==0 && $y==0) || ($x==0 && $y==$height-1) ||
- ($x==($width-1) && $y==0) ||
- ($x==($width-1) && $y==($height-1)))
- {
- $density=$border_density;
- }
- if ($density!=$border_density &&
- ($x<$min_distance/2 || $y<$min_distance/2 ||
- $x>($width-$min_distance/2) ||
- $y>($height-$min_distance/2)))
- {
- continue;
- }
- if (mt_rand(0,10000)<=$density) {
- $p[]=array(
- 'x'=>$x,
- 'y'=>$y,
- );
- }
- }
- }
Code (PHP) : Убрать нумерацию
- //--------------------------------------------------------------
- // Очистка близко расположенных точек
- //--------------------------------------------------------------
- $fixed=array(
- array(
- 'x'=>0,
- 'y'=>0,
- ),
- array(
- 'x'=>($width-1),
- 'y'=>0,
- ),
- array(
- 'x'=>($width-1),
- 'y'=>($height-1),
- ),
- array(
- 'x'=>0,
- 'y'=>($height-1),
- ),
- );
- for($i=0; $i<count($p); $i++) {
- $pt=$p[$i];
- if (($pt['x']==0 && $pt['y']==0) ||
- ($pt['x']==($width-1) && $pt['y']==0) ||
- ($pt['x']==0 && $pt['y']==($height-1)) ||
- ($pt['x']==($width-1) && $pt['y']==($height-1)))
- {
- continue;
- }
- $too_close=false;
- for ($j=0; $j<count($fixed); $j++) {
- $vt=$fixed[$j];
- $dX = $vt['x']-$pt['x'];
- $dY = $vt['y']-$pt['y'];
- $dist = sqrt($dX*$dX + $dY*$dY);
- if ($dist<$min_distance) {
- $too_close=true;
- break;
- }
- }
- if (!$too_close) {
- $fixed[]=$pt;
- }
- }
- $p=$fixed;
Code (PHP) : Убрать нумерацию
- //--------------------------------------------------------------
- // 2D-триангуляция Делоне
- //--------------------------------------------------------------
- $tri=array();
- $z=array();
- for ($i=0; $i<count($p); $i++) {
- $z[$i]=$p[$i]['x']*$p[$i]['x'] + $p[$i]['y']*$p[$i]['y'];
- }
- for ($i=0; $i<(count($p)-2); $i++) {
- for ($j=($i+1); $j<count($p); $j++) {
- for ($k=($i+1); $k<count($p); $k++) {
- if ($j!=$k) {
- $xn=($p[$j]['y']-$p[$i]['y']) * ($z[$k]-$z[$i]) -
- ($p[$k]['y']-$p[$i]['y']) * ($z[$j]-$z[$i]);
- $yn=($p[$k]['x']-$p[$i]['x']) * ($z[$j]-$z[$i]) -
- ($p[$j]['x']-$p[$i]['x']) * ($z[$k]-$z[$i]);
- $zn=($p[$j]['x']-$p[$i]['x']) * ($p[$k]['y']-$p[$i]['y']) -
- ($p[$k]['x']-$p[$i]['x']) * ($p[$j]['y']-$p[$i]['y']);
- $flag=($zn<0);
- if ($flag) {
- for ($m=0; $m<count($p); $m++) {
- $flag=$flag && ((
- ($p[$m]['x']-$p[$i]['x']) * $xn +
- ($p[$m]['y']-$p[$i]['y']) * $yn +
- ($z[$m]-$z[$i]) * $zn) <=0
- );
- }
- }
- if ($flag) {
- $tri[]=array($i, $j, $k);
- }
- }
- }
- }
- }
Изображение с нанесенной сеткой
Нам останется только посчитать основной цвет в каждом треугольнике и затем выполнить заливку. В отличие от квадратных пикселей, тут придется проверять каждую точку изображения на предмет принадлежности тому или иному треугольнику. Снова тяжеленные вложенные циклы, снова потеря времени. Но увы, ничего не поделаешь. Небольшая оптимизация может быть достигнута за счет сохранения информации, какому треугольнику какая точка принадлежит, чтобы не тратить на это время при заливке. Тут используется вспомогательная функция, которой на вход подаются координаты проверяемой точки и координаты вершин треугольника, а на выходе она возвращает результат, принадлежит ли эта точка треугольнику или нет.
Code (PHP) : Убрать нумерацию
- //--------------------------------------------------------------
- // Проверить принадлежность точек треугольникам
- //--------------------------------------------------------------
- function point_in_triangle($pt, $p1, $p2, $p3) {
- $side_1=($pt['x']-$p2['x']) * ($p1['y']-$p2['y']) -
- ($p1['x']-$p2['x']) * ($pt['y']-$p2['y']);
- $side_2=($pt['x']-$p3['x']) * ($p2['y']-$p3['y']) -
- ($p2['x']-$p3['x']) * ($pt['y']-$p3['y']);
- $side_3=($pt['x']-$p1['x']) * ($p3['y']-$p1['y']) -
- ($p3['x']-$p1['x']) * ($pt['y']-$p1['y']);
- return (($side_1<0.0 && $side_2<0.0 && $side_3<0.0) ||
- ($side_1>=0.0 && $side_2>=0.0 && $side_3>=0.0));
- }
- $color_data=array();
- $pixel_data=array();
- for ($y=0; $y<$height; $y++) {
- for ($x=0; $x<$width; $x++) {
- $RGB=ImageColorAt($im, $x, $y);
- $R=($RGB >> 16) & 0xFF;
- $G=($RGB >> 8) & 0xFF;
- $B=$RGB & 0xFF;
- for ($i=0; $i<count($tri); $i++) {
- if (!isset($color_data[$i])) {
- $color_data[$i]=array(
- 'R'=>0,
- 'G'=>0,
- 'B'=>0,
- 'count'=>0,
- );
- }
- if (point_in_triangle(
- array('x'=>$x, 'y'=>$y),
- array('x'=>$p[$tri[$i][0]]['x'], 'y'=>$p[$tri[$i][0]]['y']),
- array('x'=>$p[$tri[$i][1]]['x'], 'y'=>$p[$tri[$i][1]]['y']),
- array('x'=>$p[$tri[$i][2]]['x'], 'y'=>$p[$tri[$i][2]]['y']))
- ) {
- $color_data[$i]['R']+=$R;
- $color_data[$i]['G']+=$G;
- $color_data[$i]['B']+=$B;
- $color_data[$i]['count']++;
- $pixel_data[$x][$y]=$i;
- break;
- }
- }
- }
- }
- //--------------------------------------------------------------
- // Высчитать средний цвет в каждом треугольнике
- //--------------------------------------------------------------
- for ($i=0; $i<count($tri); $i++) {
- $color_data[$i]['R']/=$color_data[$i]['count'];
- $color_data[$i]['G']/=$color_data[$i]['count'];
- $color_data[$i]['B']/=$color_data[$i]['count'];
- }
- //--------------------------------------------------------------
- // Закрасить точки в каждом треугольнике
- //--------------------------------------------------------------
- for ($y=0; $y<$height; $y++) {
- for ($x=0; $x<$width; $x++) {
- $color=ImageColorAllocate($im,
- $color_data[$pixel_data[$x][$y]]['R'],
- $color_data[$pixel_data[$x][$y]]['G'],
- $color_data[$pixel_data[$x][$y]]['B']
- );
- imagesetpixel($im,$x,$y,$color);
- }
- }
Code (PHP) : Убрать нумерацию
- //--------------------------------------------------------------
- // Нарисовать сетку витража
- //--------------------------------------------------------------
- $white=ImageColorAllocate($im, 255, 255, 255);
- foreach($tri as $tr) {
- imagepolygon($im, array(
- $p[$tr[0]]['x'], $p[$tr[0]]['y'],
- $p[$tr[1]]['x'], $p[$tr[1]]['y'],
- $p[$tr[2]]['x'], $p[$tr[2]]['y'],
- ), 3, $white);
- }
Просмотров: 636 | Комментариев: 2
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(16.02.2022 в 13:15):
Так это ж она и есть.
Grey
(16.02.2022 в 08:07):
Напомнило триангуляцию
Добавить комментарий
Заполните форму для добавления комментария