Generator zestawu Mandelbrot PHP

Sep 04 2020

Muszę się zapoznać z programowaniem internetowym, więc zdecydowałem się napisać dość prostą stronę PHP, która przyjmuje żądania GET i wyświetla obraz zbioru Mandelbrota ... jako tabelę kolorowych komórek.

Przykłady:

i

Wysyłam odpowiedź jako tabelę, ponieważ nie chciałem jeszcze zajmować się korzystaniem z bibliotek obrazów, więc chciałem spróbować zrobić to całkowicie przy użyciu HTML i nie mogłem wymyślić prostszego sposobu.

Zasadniczo, jak to działa, wskazujesz miejsce w zestawie, które chcesz wyświetlić, określając pola „Granice”, rozmiar widoku i tabeli, współczynnik proporcji (jako ułamek dziesiętny), a następnie dziewięć kolorowych pól, które decydują o sposobie obrazu będzie kolorowy.

Główną logiką jest zasadniczo port mojego kodu C z zeszłego roku , a pomysł kolorowania został zaczerpnięty z projektu, który zrobiłem kilka lat temu w Clojure.

Szukam sugestii na wszystko. Nie używałem HTML, PHP ani CSS od lat i nigdy nie byłem z nimi świetny. Jest tu dużo, więc nie oczekuję super dokładnych recenzji, ale wszystko byłoby docenione.

W szczególności jednak:

  • Funkcja new_color_f()to katastrofa. Zasadniczo chcę mieć możliwość utworzenia funkcji, która ma już ustawione niektóre parametry, i robię to, zamykając parametry funkcji otaczającej. Jest jednak długi i brzydki, a useskładnia PHP go pogarsza . Wszelkie sugestie będą mile widziane.

  • Funkcja również defaulting_get()wydaje się być zapachem. Muszę uwzględnić brakujące dane GET i potencjalnie puste. Doprowadziło to jednak do zastosowania dziwacznej kombinacji operatorów ??i ?:.

Wszystko oprócz:

  • Tak, robienie tego w PHP jest głupie, tak jak ja, a także głupotą jest robienie tego za pomocą HTML table. Powoduje to problemy z czyszczeniem formularza wejściowego przy każdym wpisie, a także powoduje niesamowite opóźnienia przeglądarki podczas odbierania większych tabel. Chciałem zdobyć trochę doświadczenia z myślą o PHP, zanim spróbuję czegoś bardziej skomplikowanego i zawsze uwielbiałem ten projekt. Naprawdę powinienem to robić w JavaScript lub używać AJAX do żądania obrazów zamiast korzystania z formularza.

W celach informacyjnych na serwerze działa PHP 7.4.


index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mandelbrot Generator</title>
    <style>            
        .display_table {
            font-size: 1px;
            border-spacing: 0;
        }
      
        .input_form {
            display: table;
        }
        
        .input_row {
            display: table-row;
        }
        
        .input_row label, .input_row input {
            display: table-cell;
        }
        
        .red_input input, .green_input input, .blue_input input {
            border-width: 5px;
        }
        
        .red_input input {
            border-color: red;
        }

        .green_input input {
            border-color: lightgreen;
        }

        .blue_input input {
            border-color: blue;
        }
        
    </style>
</head>
<body>
    <form method="get" class="input_form">
        <div class="input">
            <div class="location_input">
                <div class="input_row">
                    <label>Lower Real Bound</label>
                    <input name="lreal" type="number" min="-2" max="2" step="any">
                </div>
                <div class="input_row">
                    <label>Lower Imaginary Bound</label>
                    <input name="limag" type="number" min="-2" max="2" step="any">
                </div>
        
                <div class="input_row">
                    <label>View Width</label>
                    <input name="vwidth" type="number" min="0" max="4" step="any">
                </div>
                <div class="input_row">
                    <label>Pixels Wide</label>
                    <input name="pwidth" type="number" min="0" max="1000" step="any">
                </div>
        
                <div class="input_row">
                    <label>Aspect Ratio</label>
                    <input name="aratio" type="number" min="0" max="2" step="any">
                </div>
            </div>

            <div class="color_input">
                <div class="red_input">
                    <div class="input_row">
                        <label>Real</label>
                        <input name="rr" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Imaginary</label>
                        <input name="rim" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Iters</label>
                        <input name="rit" type="number" min="-1000" max="1000" step="any">
                    </div>
                </div>

                <div class="green_input">
                    <div class="input_row">
                        <label>Real</label>
                        <input name="gr" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Imaginary</label>
                        <input name="gim" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Iters</label>
                        <input name="git" type="number" min="-1000" max="1000" step="any">
                    </div>
                </div>

                <div class="blue_input">
                    <div class="input_row">
                        <label>Real</label>
                        <input name="br" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Imaginary</label>
                        <input name="bim" type="number" min="-1000" max="1000" step="any">
                    </div>
                    <div class="input_row">
                        <label>Iters</label>
                        <input name="bit" type="number" min="-1000" max="1000" step="any">
                    </div>
                </div>
                
            </div>
        </div>

        <button type="submit">Submit</button>
    </form>
 
</body>
</html>

<?php
    include "display.php";
    
    const DEF_LOW_REAL = -2;
    const DEF_LOW_IMAG = -2;
    const DEF_VIEW_WIDTH = 4;
    const DEF_PIX_WIDTH = 100;
    const DEF_ASP_RATIO = 1;
    const DEF_COLOR_MULT = 2;
    
    function defaulting_get($key, $default) {
        return ($_GET[$key] ?? $default) ?: $default;
    }
    
    $low_real = defaulting_get("lreal", DEF_LOW_REAL); $low_imag = defaulting_get("limag", DEF_LOW_IMAG);
    $view_width = defaulting_get("vwidth", DEF_VIEW_WIDTH); $pixels_wide = defaulting_get("pwidth", DEF_PIX_WIDTH);
    $aspect_ratio = defaulting_get("aratio", DEF_ASP_RATIO); $view_height = $view_width / $aspect_ratio;
    $high_real = $low_real + $view_height; $high_imag = $low_imag + $view_width;
    $pixels_high = $pixels_wide / $aspect_ratio; $color_f = new_color_f(
        defaulting_get("rr", DEF_COLOR_MULT),
        defaulting_get("rim", DEF_COLOR_MULT),
        defaulting_get("rit", DEF_COLOR_MULT),
        defaulting_get("gr", DEF_COLOR_MULT),
        defaulting_get("gim", DEF_COLOR_MULT),
        defaulting_get("git", DEF_COLOR_MULT),
        defaulting_get("br", DEF_COLOR_MULT),
        defaulting_get("bim", DEF_COLOR_MULT),
        defaulting_get("bit", DEF_COLOR_MULT),
    );

    emit_mandelbrot_view(
        $color_f, $low_real,
        $high_real, $low_imag,
        $high_imag, $pixels_wide,
        $pixels_high);

display.php

<?php
    include "iteration.php";
    
    const COLOR_MAX = (2 << 7) - 1;
    
    function clamp_color($n) {
        return max(0, min(COLOR_MAX, $n)); } function checked_produce_rgb($red, $green, $blue) {
        $c_red = clamp_color($red);
        $c_green = clamp_color($green);
        $c_blue = clamp_color(($blue));
        return "rgb($c_red, $c_green, $c_blue)"; } function new_color_f($red_real, $red_imag, $red_iter,
                         $gre_real, $gre_imag, $gre_iter, $blu_real, $blu_imag, $blu_iter) {
        
        $color_func = function($real, $imag, $iters) use
                              ($red_real, $red_imag, $red_iter, $gre_real, $gre_imag, $gre_iter,
                               $blu_real, $blu_imag, $blu_iter) { return checked_produce_rgb( $real * $red_real + $imag * $red_imag + $iters * $red_iter, $real * $gre_real + $imag * $gre_imag + $iters * $gre_iter, $real * $blu_real + $imag * $blu_imag + $iters * $blu_iter ); }; return $color_func;
    }

    function produce_pixel($color) { return "<td style='background: $color; color: $color'>_</td>"; } function emit_mandelbrot_view($color_f,
                                  $lower_real, $upper_real,
                                  $lower_imag, $upper_imag,
                                  $pixels_wide, $pixels_high) {
        
        $real_step = ($upper_real - $lower_real) / $pixels_wide;
        $imag_step = ($upper_imag - $lower_imag) / $pixels_high;
        
        echo "<table class='display_table'>";
        for ($imag = $lower_imag; $imag <= $upper_imag; $imag += $imag_step) {
            echo "<tr>";
            for ($real = $lower_real; $real <= $upper_real; $real += $real_step) {
                $iters = test_point([$real, $imag]); $color = $color_f($real, $imag, $iters);
                
                echo produce_pixel($color);
            }
            echo "</tr>";
        }
        echo "</table>";
    }

iteration.php

<?php
    // Make mutative?
    
    const STD_MAX_ITERS = 200;
    const STD_INF_LIMIT = 2;

    function square_complex($complex) {
        [$real, $imag] = $complex; return [($real * $real) - ($imag * $imag), 2 * $real * $imag]; } function mandelbrot_iteration($init_complex, $curr_complex) { [$i_real, $i_imag] = $init_complex;
        $sqrd = square_complex($curr_complex);
        
        $sqrd[0] += $i_real;
        $sqrd[1] += $i_imag;
        
        return $sqrd; } function is_under_limit($complex, $inf_limit) { [$real, $imag] = $complex;
        return ($real * $real) + ($imag * $imag) <= ($inf_limit * $inf_limit);
    }
    
    function test_point($initial_complex, $max_iters = STD_MAX_ITERS,
                        $inf_limit = STD_INF_LIMIT) { $current = $initial_complex; $i = 0;
        for (; $i < $max_iters && is_under_limit($current, $inf_limit); $i++) { $current = mandelbrot_iteration($initial_complex, $current);
        }
        
        return $i;
    }

Odpowiedzi

3 SᴀᴍOnᴇᴌᴀ Sep 05 2020 at 06:36

Interfejs użytkownika

Zauważyłem, że jeśli wprowadziłem wartości do formularza, a następnie go przesłałem, wartości nie zostały utrwalone po załadowaniu następnej strony. Byłoby miło, gdyby wartości zostały zachowane w danych wejściowych formularza. Jest to łatwe do osiągnięcia, ponieważ formularz jest renderowany przez stronę PHP.

Ponieważ kod PHP do drugiej połowy index.php pojawia się po zamknięciu HTML, HTML wygenerowany dla tabeli jest technicznie poza dokumentem HTML:

    </form>

  </body>
</html>

<table class='display_table'>

Większość nowoczesnych przeglądarek wyświetla tabelę tak, jakby znajdowała się wewnątrz <body>tagu, ale nie jest to technicznie poprawna struktura. Za pomocąhttps://validator.w3.org/ z wygenerowanym źródłem pokazuje następujące dane wyjściowe:

Powszechną konwencją jest umieszczenie większości kodu PHP na początku pliku - jest to szczególnie przydatne, jeśli istnieje funkcjonalność, która modyfikuje nagłówki HTTP, np. Wywołania, header()ponieważ nagłówki nie mogą być wysyłane po wyemitowaniu HTML.

Pamiętaj, że header()musi zostać wywołane przed wysłaniem jakichkolwiek faktycznych danych wyjściowych, albo przez normalne znaczniki HTML, puste wiersze w pliku, albo przez PHP. 1

Kod HTML wygenerowany z wywołań PHP (np. Wywołania, do emit_mandelbrot_view()których potrzebny byłby returnwygenerowany kod HTML zamiast wywoływania echobezpośrednio) może być przechowywany w zmiennej lub emitowany w tekście, opcjonalnie przy użyciu składni skrótu dla echo- tj <?=:

<?php
//setup PHP code here
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- ... HTML headers here -->
    </head>
    <body>
        <form>
        <!-- ... HTML here -->
        </form>
         <?= emit_mandelbrot_view($color_f...)?>
    </body>
</html>

Sugestie dotyczące kodu

  • new_color_fto katastrofa. Zasadniczo chcę mieć możliwość utworzenia funkcji, która ma już ustawione niektóre parametry, i robię to, zamykając parametry funkcji otaczającej. Jest jednak długi i brzydki, a dodatkowo pogarsza go składnia użycia PHP. Wszelkie sugestie będą mile widziane.

Ponieważ potwierdziłeś, że serwer, na którym działa kod, korzysta z PHP 7.4, kod można zaktualizować, aby korzystać z nowszych funkcji, takich jak funkcje strzałek

Funkcje strzałkowe obsługują te same funkcje, co funkcje anonimowe, z tym wyjątkiem, że używanie zmiennych z zakresu nadrzędnego jest zawsze automatyczne.

Umożliwiłoby to usewyeliminowanie oświadczenia w new_color_f().


Funkcji range()można użyć w foreachcelu uproszczenia zagnieżdżonych pętli w emit_mandelbrot_view():

for ($imag = $lower_imag; $imag <= $upper_imag; $imag += $imag_step) { echo "<tr>"; for ($real = $lower_real; $real <= $upper_real; $real += $real_step) {

Można uprościć do:

foreach (range($lower_imag, $upper_imag, $imag_step) as $imag) { echo "<tr>"; foreach (range($lower_real, $upper_real, $real_step) as $real) {

chociaż biorąc pod uwagę liczbę wywołań tej funkcji, dodawanie kolejnych wywołań funkcji może nie być wskazane, aby uprościć składnię.


Dla tego bloku w index.php:

    $color_f = new_color_f(
        defaulting_get("rr", DEF_COLOR_MULT),
        defaulting_get("rim", DEF_COLOR_MULT),
        defaulting_get("rit", DEF_COLOR_MULT),
        defaulting_get("gr", DEF_COLOR_MULT),
        defaulting_get("gim", DEF_COLOR_MULT),
        defaulting_get("git", DEF_COLOR_MULT),
        defaulting_get("br", DEF_COLOR_MULT),
        defaulting_get("bim", DEF_COLOR_MULT),
        defaulting_get("bit", DEF_COLOR_MULT),
    );

Oczywiście jest tu dużo duplikatów. Jednym uproszczeniem byłoby umieszczenie kluczy ciągu zapytania w tablicy i pętli nad nimi, przekazanie ich do funkcji i przechowywanie wyników w tablicy, którą można rozłożyć na wywołanie funkcji new_color_f().


W test_point()iteratorze ustawia się poza pętlą:

$i = 0; for (; $i < $max_iters && is_under_limit($current, $inf_limit); $i++) {
       $current = mandelbrot_iteration($initial_complex, $current);
}

W przeciwieństwie do kodu c $imożna zadeklarować w pierwszej instrukcji forpętli i nadal być widoczny poza pętlą, ponieważ zmienne nie są ograniczone do bloku, ale raczej do funkcji (jeśli ma to zastosowanie). Nie jest to dobrze wyjaśnione w zakresie zmiennych dokumentacji PHP, chociaż jest na ten temat komentarz .

dodothedreamer na gmail dot com ¶ 8 lat temu


Zwróć uwagę, że w przeciwieństwie do Java i C ++ zmienne zadeklarowane wewnątrz bloków, takie jak pętle lub if, będą również rozpoznawane i dostępne poza blokiem, więc:

<?php
for($j=0; $j<3; $j++) { if($j == 1)
        $a = 4; } echo $a;
?>

Wydrukowałoby 4.


Ta stała definicja wygląda jak optymalizacja w stylu C.

const COLOR_MAX = (2 << 7) - 1; 

Popraw mnie, jeśli się mylę, ale wątpię, czy istnieje jakakolwiek optymalizacja nad deklarowaniem tego jako 255:

const COLOR_MAX = 255;

PS Zapytałem o tę stałą deklarację na Stack Overflow . Z dotychczasowych odpowiedzi wydaje się, że to tylko osobiste preferencje, jeśli chodzi o deklarowanie wartości. Jeśli wolisz używać składni z przesunięciem bitowym, możesz to zrobić, ale uprzedzaj, że jeśli będziesz pracować w zespole z innymi, może pojawić się pytanie w przeglądzie kodu.

1 bdsl Sep 13 2020 at 22:55

Rodzaje

Ułatwia to zrozumienie kodu, jeśli określisz typy dla wszystkich sygnatur funkcji. PHP sprawdzi typy, jeśli zostały określone w czasie wykonywania, za każdym razem, gdy wchodzisz i wychodzisz z funkcji oraz podczas pisania do właściwości klasy, pomagając znacznie szybciej wykrywać i znajdować błędy, zwłaszcza jeśli używasz IDE lub narzędzia do analizy statycznej. Pomagają również przekazać intencję kodu, aby ułatwić dodawanie do niego kolejnych.

Kilka przykładowych zmian w celu dodania typów:

W index.php zmień nafunction defaulting_get($key, $default) {function defaulting_get(string $key, int $default): int {

W display.php zmień function clamp_color($n) {nafunction clamp_color(int $n): int {

i

    function new_color_f($red_real, $red_imag, $red_iter, $gre_real, $gre_imag, $gre_iter,
                         $blu_real, $blu_imag, $blu_iter) {

do

    /** @return \Closure(int, int, int): string */
    function new_color_f(int $red_real, int $red_imag, int $red_iter,
                         int $gre_real, int $gre_imag, int $gre_iter, int $blu_real, int $blu_imag, int $blu_iter): \Closure {

Nie jestem pewien, czy mam ten typ zamknięcia całkiem dobrze w docblocku.

Zrobiłbym to dla każdej funkcji.

Unikanie use

Możesz tego uniknąć, usekorzystając ze składni krótkiego domknięcia wprowadzonej w PHP 7.4:

        /** @return \Closure(int, int, int): string */
        function new_color_f(int $red_real, int $red_imag, int $red_iter, int $gre_real, int $gre_imag, int $gre_iter,
                            int $blu_real, int $blu_imag, int $blu_iter): \Closure { return fn(int $real, int $imag, int $iters): string => 
              checked_produce_rgb(
                $real * $red_real + $imag * $red_imag + $iters * $red_iter,
                $real * $gre_real + $imag * $gre_imag + $iters * $gre_iter,
                $real * $blu_real + $imag * $blu_imag + $iters * $blu_iter
            );
        };
    }

PHP automatycznie dołącza wszystkie zmienne wymienione po =>krótkim zamknięciu.

Klasy i automatyczne ładowanie

Nowoczesne idiomatyczne PHP, szczególnie w przypadku większych aplikacji, ma tendencję do posiadania prawie całego kodu w postaci klas, nawet jeśli aplikacja nie jest przeznaczona do zorientowania obiektowego. Jedną z zalet używania klas jest to, że system automatycznego ładowania PHP może ładować je na żądanie. Nie jest to dostępne w przypadku bezpłatnych funkcji. Funkcji statycznych na klasach można wygodnie używać zamiast funkcji swobodnych.

Zastąpienie wszystkich darmowych funkcji statycznymi funkcjami klasowymi umożliwi również oznaczenie wszelkich funkcji używanych tylko w klasie, która je deklaruje private, co powinno znacznie ułatwić zrozumienie przepływu kodu.

Typowym sposobem korzystania z automatycznego ładowania jest skonfigurowanie projektu za pomocą Composera, zorganizowanie jednej klasy w każdym pliku, przestrzeganie reguł PSR-4 dotyczących lokalizacji i nazwy każdego pliku, a także zmusić Composera do wygenerowania pliku autoload.php . Można to wywołać z lub require_oncez index.phpdowolnego innego punktu wejścia. Wszelkie potrzebne później klasy zostaną automatycznie załadowane, jeśli można je znaleźć.

Zrobiłbym również, aby new_color_f zwrócił obiekt zamiast zamknięcia i odpowiednio go zmienił. Ten obiekt może mieć prywatne pola zamiast zamkniętych zmiennych i funkcję getRGBpubliczną.