7 января 2011 в 18:46

Бикубическая интерполяция, теория и практическая реализация из песочницы

PHP*
Возникла задача визуализировать результаты некоторых замеров на 2-мерной карте, были известны результаты в узловых точках на равномерной сетке, соответственно, задача свелась к интерполяции полученных данных. Основное требование было — качество полученной картинки и минимальное количество артефактов интерполяции, поэтому выбор пал на бикубическую интерполяцию. Статьи в Вики мне показались суховатыми (по крайней мере для человека, который математикой не занимался со школьной скамьи), но там же нашлась ссылка на потрясающую статью, детально описывающую алгоритм. Здесь мы рассмотрим практическое применение данного алгоритма и разберем статью.

Терминология


Интерполя́ция, интерполи́рование — в вычислительной математике способ нахождения промежуточных значений величины по имеющемуся дискретному набору известных значений.

(с) Вики

Кубическая интерполяция



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

Если значения функции f(x) и ее производной известны в точках x=0 и x=1, тогда функция может быть интерполирована на интервале [0, 1] используя полином третьего порядка. Формула для вычисления может быть легко получена.

Полином третьего порядка и его производная:



Значения полинома и его производной в точках x=0 и x=1





Эти четыре тождества могут быть записаны как:





Итак мы получили нашу интерполяционную формулу

На практике алгоритм используют для интерполяции функции, имея некие известные значения в заданных точках. В этом случае мы не можем знать производную функции. Мы могли бы принять производную в заданных точках, как 0, однако для получения более гладких и правдоподобных графиков функций мы примем за производную уклон линии между предыдущей и следующей точкой. Таким образом для расчетов нам понадобится 4 точки. Предположим, мы имеем 4 значения функции в точках p0, p1, p2 и p3, расположенных соответственно на x=-1, x=0, x=1 и x=2. Подставим полученные значения f(0), f(1), f(2) и f(3):






Сопоставив эти данные с полученными ранее формулами мы имеем:





Результат:


Рассмотрим реализацию этого алгоритма на PHP:

/**
* Кубическая интерполяция
*
* @param array $p Массив известных значений функции: f(x0), f(x1), f(x2), f(x3)
* @param float $x x-координата искомой точки
* @return float Значение функции f($x)
 */
function cubicInterpolate ($p, $x) {
    return $p[1] + (-0.5 * $p[0] + 0.5 * $p[2]) * $x
        + ($p[0] - 2.5 * $p[1] + 2.0 * $p[2] - 0.5 * $p[3]) * $x * $x
        + (-0.5 * $p[0] + 1.5 * $p[1] - 1.5 * $p[2] + 0.5 * $p[3]) * $x * $x * $x;
}


Бикубическая интерполяция



Бикубическая интерполяция представляет собой кубическую интерполяцию в двух измерения. На самом деле мы можем интерполировать и в большем количестве измерений, но в рамках данной статьи, рассмотрим только этот пример.
Представим, что нам известно 16 точек pij, с точкой начала координат в (i-1, j-1), где i,j изменяются от 0 до 3. Тогда, мы сможем интерполировать поверхность на участке [0,0] х [1,1], для этого интерполируем 4 колонки и потом интерполируем полученные результаты в горизонтальном направлении:


Реализация метода на PHP, совмещенная с первой функцией:

/**
* Функция бикубической интерполяции
*
* @param array $p Массив с 2-мя измерениями (4 x 4), содержащий известные значения 16-ти соседних точек
* @param float $x x-координата искомой точки
* @param float $y y-координата искомой точки
* @return float Значение функции f($x, $y)
 */
function bicubicInterpolate ($p, $x, $y) {
    $tmp = array();
    $tmp[0] = cubicInterpolate($p[0], $y);
    $tmp[1] = cubicInterpolate($p[1], $y);
    $tmp[2] = cubicInterpolate($p[2], $y);
    $tmp[3] = cubicInterpolate($p[3], $y);
    return cubicInterpolate($tmp, $x);
}


Все было бы здорово, если бы не одна проблема — производительность. Для того чтобы интерполировать даже относительно небольшой объем данных требуются серьезные вычислительные ресурсы. Для оптимизации быстродействия существует следующий подход.
Рассмотрим нашу формулу в виде многомерного полинома:

В развернутом виде будет что-то вроде:
g(x,y) = a00 * x^0 * y^0 + a01 * x^0 * y^1 + 
         a02 * x^0 * y^2 + a03 * x^0 * y^3 +
         a10 * x^1 * y^0 + a11 * x^1 * y^1 + 
         a12 * x^1 * y^2 + a13 * x^1 * y^3 +
         a20 * x^2 * y^0 + a21 * x^2 * y^1 + 
         a22 * x^3 * y^2 + a23 * x^2 * y^3 +
         a30 * x^3 * y^0 + a31 * x^3 * y^1 + 
         a32 * x^3 * y^2 + a33 * x^3 * y^3

Теперь необходимо вычислить коэффициенты aij. Здесь особо хотелось бы отметить, что вычислив единожды коэффициенты мы можем использовать их для интерполяции всех значений на участке от [0,0] до [1,1]

















Для реализации этого метода создадим следующий класс:

class CachedBicubicInterpolator
{
    private $a00, $a01, $a02, $a03,
            $a10, $a11, $a12, $a13,
            $a20, $a21, $a22, $a23,
            $a30, $a31, $a32, $a33;

    /**
    * Кешируем коэффициенты.
    *
    * @param array $p 2-мерный массив (4 x 4), содержащий значения известных соседних точек
    * @return void
     */
    public function updateCoefficients ($p) {
        $this->a00  = $p[1][1];
        $this->a01  = -.5 * $p[1][0] + .5 * $p[1][2];
        $this->a02  = $p[1][0] - 2.5 * $p[1][1] + 2 * $p[1][2] - .5 * $p[1][3];
        $this->a03  = -.5 * $p[1][0] + 1.5 * $p[1][1] - 1.5 * $p[1][2] + .5 * $p[1][3];
        $this->a10  = -.5 * $p[0][1] + .5 * $p[2][1];
        $this->a11  = .25 * $p[0][0] - .25 * $p[0][2] - .25 * $p[2][0] + .25 * $p[2][2];
        $this->a12  = -.5 * $p[0][0] + 1.25 * $p[0][1] - $p[0][2] + .25 * $p[0][3]
                    + .5 * $p[2][0] - 1.25 * $p[2][1] + $p[2][2] - .25 * $p[2][3];
        $this->a13  = .25 * $p[0][0] - .75 * $p[0][1] + .75 * $p[0][2] - .25 * $p[0][3]
                    - .25 * $p[2][0] + .75 * $p[2][1] - .75 * $p[2][2] + .25 * $p[2][3];
        $this->a20  = $p[0][1] - 2.5 * $p[1][1] + 2 * $p[2][1] - .5 * $p[3][1];
        $this->a21  = -.5 * $p[0][0] + .5 * $p[0][2] + 1.25 * $p[1][0] - 1.25 * $p[1][2]
                    - $p[2][0] + $p[2][2] + .25 * $p[3][0] - .25 * $p[3][2];
        $this->a22  = $p[0][0] - 2.5 * $p[0][1] + 2 * $p[0][2] - .5 * $p[0][3] - 2.5 * $p[1][0]
                    + 6.25 * $p[1][1] - 5 * $p[1][2] + 1.25 * $p[1][3] + 2 * $p[2][0]
                    - 5 * $p[2][1] + 4 * $p[2][2] - $p[2][3] - .5 * $p[3][0]
                    + 1.25 * $p[3][1] - $p[3][2] + .25 * $p[3][3];
        $this->a23  = -.5 * $p[0][0] + 1.5 * $p[0][1] - 1.5 * $p[0][2] + .5 * $p[0][3]
                    + 1.25 * $p[1][0] - 3.75 * $p[1][1] + 3.75 * $p[1][2]
                    - 1.25 * $p[1][3] - $p[2][0] + 3 * $p[2][1] - 3 * $p[2][2] + $p[2][3]
                    + .25 * $p[3][0] - .75 * $p[3][1] + .75 * $p[3][2] - .25 * $p[3][3];
        $this->a30  = -.5 * $p[0][1] + 1.5 * $p[1][1] - 1.5 * $p[2][1] + .5 * $p[3][1];
        $this->a31  = .25 * $p[0][0] - .25 * $p[0][2] - .75 * $p[1][0] + .75 * $p[1][2]
                    + .75 * $p[2][0] - .75 * $p[2][2] - .25 * $p[3][0] + .25 * $p[3][2];
        $this->a32  = -.5 * $p[0][0] + 1.25 * $p[0][1] - $p[0][2] + .25 * $p[0][3]
                    + 1.5 * $p[1][0] - 3.75 * $p[1][1] + 3 * $p[1][2] - .75 * $p[1][3]
                    - 1.5 * $p[2][0] + 3.75 * $p[2][1] - 3 * $p[2][2] + .75 * $p[2][3]
                    + .5 * $p[3][0] - 1.25 * $p[3][1] + $p[3][2] - .25 * $p[3][3];
        $this->a33  = .25 * $p[0][0] - .75 * $p[0][1] + .75 * $p[0][2] - .25 * $p[0][3]
                    - .75 * $p[1][0] + 2.25 * $p[1][1] - 2.25 * $p[1][2] + .75 * $p[1][3]
                    + .75 * $p[2][0] - 2.25 * $p[2][1] + 2.25 * $p[2][2] - .75 * $p[2][3]
                    - .25 * $p[3][0] + .75 * $p[3][1] - .75 * $p[3][2] + .25 * $p[3][3];
    }
    /**
    * Получаем значения.
    *
    * @param float $x $x = $x - $x0
    * @param float $y $y = $y - $y0
    * @return float Значение функции в точке.
     */
    public function getValue ($x, $y) {
            //Кешируем возвещение во 2 и 3 степени.
            $x2 = $x * $x;
            $x3 = $x2 * $x;
            $y2 = $y * $y;
            $y3 = $y2 * $y;

            return ($this->a00 + $this->a01 * $y + $this->a02 * $y2 + $this->a03 * $y3) +
                   ($this->a10 + $this->a11 * $y + $this->a12 * $y2 + $this->a13 * $y3) * $x +
                   ($this->a20 + $this->a21 * $y + $this->a22 * $y2 + $this->a23 * $y3) * $x2 +
                   ($this->a30 + $this->a31 * $y + $this->a32 * $y2 + $this->a33 * $y3) * $x3;
    }
}


Практическое применение



Рассмотрим применение бикубической интерполяции на следующем примере:
Исходные данные: массив данных 7 х 7 точек
Требуется: Представить поверхность в виде изображения 300 х 300 пикселей.

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

<?php
header("Content-type: image/png");//Меняем заголовок
include 'interpolation.php';//Подключаем необходимые файлы
include 'colormap.php';//Подключаем необходимые файлы

$simpleData = array(
    array(0, 0.1, 0.15, 0.2, 0.15, 0.1, 0),
    array(0, 0.1, 0.5, 1, 0.9, 0.1, 0),
    array(0, 0.1, 0.7, 1, 0.9, 0.1, 0),
    array(0, 0.1, 0.6, 0.7, 0.5, 0.1, 0),
    array(0, 0.2, 0.8, 0.6, 0.15, 0.08, 0.1),
    array(0.08, 0.2, 0.15, 0.2, 0.15, 0.1, 0.1),
    array(0.1, 0.8, 0.1, 0.1, 0.1, 0, 0)
);//Пример массива исходных данных 7 х 7

$countX = count($simpleData[0]);//Определяем размерность входных данных
$countY = count($simpleData);
$input = array();

/* Экстраполируем и перевернем массив для удобства
 * Линейно экстраполируем крайние строки и столбцы, для того чтобы можно было интерполировать крайние квадраты
 * В случае заполнения нулями - граница будет выглядеть слишком грубо
 */
for ($i = 0; $i < $countX; $i ++) {
    for ($j = 0; $j < $countY; $j ++) {
        $input[$i + 1][$j + 1] = $simpleData[$j][$i];
        if ($i == 0) {
            $input[0][$j + 1] = $simpleData[$j][$i] - $simpleData[$j][$i + 1];//экстраполируем влево
        }
        if ($i == ($countX - 1)) {
            $input[$countX + 1][$j + 1] = $simpleData[$j][$i] - $simpleData[$j][$i - 1];//экстраполируем вправо
        }
        if ($j == 0) {
            $input[$i + 1][0] = $simpleData[$j][$i] - $simpleData[$j + 1][$i];//экстраполируем вверх
        }
        if ($j == ($countY - 1)) {
            $input[$i + 1][$countY + 1] = $simpleData[$j][$i] - $simpleData[$j - 1][$i];//экстраполируем вниз
        }
    }
}
/* Экстраполируем углы
 * Особой точности здесь не требуется, поэтому я использовал среднее арифметическое.
 */
$input[0][0] = ($input[1][0] + $input[0][1]) / 2;
$input[$countX + 1][0] = ($input[$countX][0] + $input[$countX][1]) / 2;
$input[0][$countY + 1] = ($input[0][$countY] + $input[1][$countY]) / 2;
$input[$countX + 1][$countY + 1] = ($input[$countX + 1][$countY] + $input[$countX][$countY]) / 2;

$step = 50; //Шаг сетки 50 пикселей
$img = imagecreatetruecolor(($countX - 1) * $step, ($countY - 1) * $step);//Создаем изображение искомого размера
$colormap = createColormap($img);//Создаем карту цветов


$b2i = new CachedBicubicInterpolator();
for ($i = 1; $i < $countX; $i ++) {//Обход исходной сетки
    for ($j = 1; $j < $countY; $j ++) {
        $points = array();
        $points[0][0] = $input[$i - 1][$j - 1];
        $points[1][0] = $input[$i][$j - 1];
        $points[2][0] = $input[$i + 1][$j - 1];
        $points[3][0] = $input[$i + 2][$j - 1];

        $points[0][1] = $input[$i - 1][$j];
        $points[1][1] = $input[$i][$j];
        $points[2][1] = $input[$i + 1][$j];
        $points[3][1] = $input[$i + 2][$j];

        $points[0][2] = $input[$i - 1][$j + 1];
        $points[1][2] = $input[$i][$j + 1];
        $points[2][2] = $input[$i + 1][$j + 1];
        $points[3][2] = $input[$i + 2][$j + 1];

        $points[0][3] = $input[$i - 1][$j + 2];
        $points[1][3] = $input[$i][$j + 2];
        $points[2][3] = $input[$i + 1][$j + 2];
        $points[3][3] = $input[$i + 2][$j + 2];
        $b2i->updateCoefficients($points);//Обновим коэффициенты

        for ($x = 0; $x < $step; $x ++) {//Обход результирующего изображения
            for ($y = 0; $y < $step; $y ++) {
                $rx = ($i - 1) * $step + $x;//х пикселя на выходном изображении
                $ry = ($j - 1) * $step + $y;//y пикселя на выходном изображении

                $ax = $x / $step;//x - x0
                $ay = $y / $step;//y - y0
                $val = $b2i->getValue($ax, $ay);//Получим значение

                $color = ceil(($val) * 1024) - 1;//Подберем цвет

                if ($color < 0) $color = 0;//На всякий случай
                if ($color > 1023) $color = 1023;//На всякий случай
                imagesetpixel($img, $rx, $ry, $colormap[$color]);//Нарисовали точку
            }
        }
    }
}
imagepng($img);
?>


Отдельно вынес файл для создания карты цветов

function createColormap(&$image) {
    $colormap = array();
    for ($i = 0; $i < 256; $i ++) {
        $colormap[$i] = imagecolorallocate($image, 0, $i, 255);
    }
    for ($i = 0; $i < 256; $i ++) {
        $colormap[$i + 256] = imagecolorallocate($image, 0, 255, 255 - $i);
    }
    for ($i = 0; $i < 256; $i ++) {
        $colormap[$i + 512] = imagecolorallocate($image, $i, 255, 0);
    }
    for ($i = 0; $i < 256; $i ++) {
        $colormap[$i + 768] = imagecolorallocate($image, 255, 255 - $i, 0);
    }
    return $colormap;
}


Результат работы:


+57
8953
85

Комментарии (34)

+29
Vladson, #
И пусть после этого хоть кто-то скажет что среди РНР программистов одна школота…
–8
Ramzeska, #
Точно школота, потому как imagecopyresampled именно для этого и существует.
Я вот не понимаю, зачем интерпретатор так мучить циклами с вычислениями?! Писал бы на компиляторе — ни слова бы не возразил, но на пхп такие вещи делать — просто кощунство.

Автор, если не сложно, покажи время работы скрипта — очень любопытно.
+1
Vladson, #
Вообще-то суть скрипта (как я понял) не только в увеличении картинки, а именно в реализации конкретного алгоритма.
+2
discourage, #
На самом деле PHP уже давно байткод интерпретатор. Для конкретной задачи производительности хватало, с учетом кеширования. Почему без встроенных функций GD и Imagick написал ниже. habrahabr.ru/blogs/php/111402/#comment_3553499
+2
discourage, #
По времени работы: на домашнем компьютере 2 Мпикс картинка генерируется чуть менее 5 секунд.
0
RageNoir, #
Все же интересно, картинку каких замеров мы видим?
0
discourage, #
В данном примере — случайные данные, в рабочем приложении использовалось для изображения карт рассеивания и распределения веществ в разных природных средах.
+1
Laytlas, #
А почему не Labview?
0
discourage, #
Требовалось построение в реальном времени, с сайта. Проект был полностью готов, в него добавили только карты рассеивания и построение изолиний. Производительность вполне устраивала, результаты кешировались.
0
Veliant, #
В том семестре в курсе моделирования тоже делал интерполяцию на PHP. И вообще, уже вошло в обиход разовые бытовые задачки делать на нем, а также набросочные. Т.к. быстро и удобно можно получить результат, не заморачиваясь на внешний вид
+3
Laytlas, #
о_О, Вы называете это быстро?
0
seriyPS, #
Попробуйте Python, правда.
–5
xaxaTyH, #
Круто, здорово, зачем?
+5
tzlom, #
это здорово
наверное весь день потратили
но ещё есть куда расти :)

$simpleData = array(
    array(0, 0.1, 0.15, 0.2, 0.15, 0.1, 0),
    array(0, 0.1, 0.5, 1, 0.9, 0.1, 0),
    array(0, 0.1, 0.7, 1, 0.9, 0.1, 0),
    array(0, 0.1, 0.6, 0.7, 0.5, 0.1, 0),
    array(0, 0.2, 0.8, 0.6, 0.15, 0.08, 0.1),
    array(0.08, 0.2, 0.15, 0.2, 0.15, 0.1, 0.1),
    array(0.1, 0.8, 0.1, 0.1, 0.1, 0, 0)
);

$image = new imagick();
$image->newImage(7,7,'black');
$draw = new ImagickDraw();

function toColorString($height)
{
        $height*=1000;
        if($heightsetColorValue(imagick::COLOR_GREEN,$c);
                $draw->setFillColor($pixel);
                $draw->point($x,$y);
        }
}

$image->drawImage($draw);
$image->setImageInterpolateMethod(imagick::INTERPOLATE_BICUBIC);
$image->resizeImage(300,300,0,1);
$iterator = $image->getPixelIterator();
foreach($iterator as $y=>$pixels)
{
        foreach($pixels as $x=>$pixel)
        {
                $pixel->setColor( toColorString($pixel->getColorValue(imagick::COLOR_GREEN)) );
        }
        $iterator->syncIterator();
}

$image->writeImage('out.png');
+1
discourage, #
Подобный вариант рассматривался, но этот же массив данных использовался еще для построения карт изолиний. Не спорю, что можно было бы реализовать попиксельное преобразование результата Imagic'а, но любопытство взяло верх.
0
tzlom, #
изолинии можно получить из этой карты :)
кстати сейчас заметил что говнокодец в функции toColorString был съеден хаброй, причём мощно выжрал, хотя я уже понял почему,damit,habrahabr!
если нужен рабочий вариант-в ЛС
я к тому что если бы этот код был хоть капельку сложен я бы не стал его писать(лень), а вы потратили кучу времени на радостные велики :)
0
discourage, #
Если не сложно не могли бы поделиться алгоритмом получения изолиний из готового изображения? Пробовал поиском граничных точек, сначала делал выборку по вертикали, потом по горизонтали, получалась довольно замыленная линия, требующая постпроцессинга для сглаживания. Сам в последствии выбрал и реализовал классический алгоритм: с помощью вышеуказанного алгоритма вычислял узловые точки с шагом примерно 5 пикселей, потом находил ребра с пересечениями и достраивал линии с помощью билинейной интерполяции.
0
RReverser, #
Лично я реализовывал в свое время гессиану на PHP, вроде как получилось, правда для вычисления производной по изображению пришлось некоторые ухищрения произвести.
0
KL7, #
Где же вы были, когда я месяц назад лабораторную писал =) Хотя там довелось еще реализовать и бикубический эрмитов сплайн. Однако, когда это все заработало да еще и изображение позумило — радости было немерено.
+3
Nikelandjelo, #
А у вас вроде не совсем честная интерполяция. При интерполяции кубическими сплайнами там накладывается условие на непрерывность самой функции, её первой и второй производной. У вас получается, что функция непрерывна, а также её первая производная. А по второй производной непрерывности нет, по-моему.
+1
discourage, #
Да, гладкость бикубической интерполяции на самом деле ниже чем у кубической. Непрерывность второй производной не обеспечивается. Обеспечивается непрерывность первых частных производных и смешанной частной производной.
0
niko83, #
когда идёт речь об обработке экспериментальных данных, каких-то результатов замеров. Используют методы аппроксимации. Иначе одно ошибочное, случайное измерение и результат будет неожиданным.
0
discourage, #
Отчасти согласен, однако и этот подход не лишен недостатков. Аппроксимация бессмысленна, если точки слабо влияют друг на друга или не влияют вовсе, если существуют локальные пики, которые может сгладить аппроксимация. Для более точного расчета, например, распределения химических веществ в природных средах строится модель для каждого вещества, с учетом метеопараметров, параметров рельефа и специфики заданного вещества. В онлайне такие вещи не считаются, потому что даже на очень мощных компьютерах сколь-нибудь достоверная модель может рассчитываться несколько минут и даже часов (в зависимости от площади территории). Для визуализации регулярных отчетов, данные по которым проверяют специалисты в ручную — может сгодиться и интерполяция.
0
guyfawkes, #
Извините, я ошибаюсь, или все-таки можно в функции createColormap все сделать в одном цикле?
0
discourage, #
4 цикла исключительно для наглядности реализации цветовых переходов. Еще одна особенность: когда я делал полупрозрачную палитру, для каждого цвета выставлял немного различающиеся значения для альфа-канала, для улучшения восприятия градиента.
0
seriyPS, #
Я только не понял, неужто бикубическую интерполяцию нужно писать САМОМУ да еще и на PHP 0_o? Неужто нет готовой математической библиотеки? Или почему не использовать ресайз картинки из ImageMagick? Там вроде есть фильтр с бикубической интерполяцией?
–5
akalend, #
>Я только не понял, неужто бикубическую интерполяцию нужно писать САМОМУ да еще и на PHP
Ответ очевиден: автор просто не знает других языков.
+1
seriyPS, #
Хех ну ваша статья на 90% состоит из перевода этой статьи www.paulinternet.nl/?page=bicubic (вы на нее в начале ссылку дали), даже картинки не перезалили + практически «дословный» порт кода из статьи с Java на PHP.

Единственное что от себя добавили — практическое применение (на мой взгляд довольно страшненькое). Слабовато как то…
0
discourage, #
Как вы заметили я указал ссылку на первоисточник и сразу уточнил, что здесь будет разбор статьи и ни слова не сказано о том, что алгоритм был реализован с нуля. Теоретическая часть статьи введена для тех, кому интересна работа самого алгоритма. К сожалению, русскоязычного подробного описания реализации данного алгоритма я не нашел, поэтому за основу был взят вышеуказанный материал, от себя прокомментировав важные, как мне показалось, моменты. Практическую реализацию подготовил специально для публикации, стараясь обеспечить легкость чтения кода. Если вы считаете, что приведенный пример можно существенно улучшить, то поделитесь, я к обоснованной критике всегда относился с уважением.
0
halyavin, #
OMG. Неужели вы не знаете, что бикубическая интерполяция — это кубическая интерполяция по одной переменной, после которой следует кубическая интерполяция по другой переменной. Аналогично для трикубической нтерполяции. И никакие матрицы 4x4 не нужны.
0
discourage, #
Вы невнимательно прочитали. В статье описаны оба подхода. Коэффициенты, нужны для оптимизации расчетов, например, Если у вас шаг сетки 100 пикселей, то решением «в лоб» придется выполнить примерно в 10 000 раз больше операций.
0
pro100tak, #
Код «вырвиглаз», хоть и причёсан. Интересно, автор — сколько понадобилось времени его сделать + отловить непроизвольные ошибки?
0
discourage, #
А в чем «вырвиглазность»? Хотя должен признать — сам пример специфичен, работа с матрицей 4х4 не очень хорошо воспринимается взглядом. Код примера был написан и отлажен примерно за час-полтора, вычислительные функции хранились давно, все «подводные камни», например, необходимость экстраполяции на 1 шаг по краям, были известны до этого.
0
pro100tak, #
Вырвиглавность именно в количестве символов/слов/понятности в строке, слабо ассоциирующемся с нормальным пониманием кода. Просто я мало видел тиках задач на PHP. Cool, enjoy your work, bro!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.