PHP Mandelbrot Set Generator

Sep 04 2020

Ho bisogno di familiarizzare con la programmazione web, quindi ho deciso di scrivere una pagina PHP abbastanza semplice che accetti le richieste GET e restituisca un'immagine dell'insieme di Mandelbrot ... come una tabella di celle colorate.

Esempi:

e

Invio la risposta come tabella perché non volevo ancora utilizzare le librerie di immagini, quindi volevo provare a farlo interamente utilizzando HTML e non potevo pensare a un modo più semplice.

Fondamentalmente come funziona è, indichi dove nel Set vuoi visualizzare specificando i campi "Limiti", la dimensione della vista e della tabella, le proporzioni (come decimale), poi nove campi colorati che decidono come l'immagine sarà colorato.

La logica principale è essenzialmente un port del mio codice C dell'anno scorso e l'idea di colorare è estratta da un progetto che ho fatto diversi anni fa in Clojure.

Sto cercando suggerimenti su qualsiasi cosa. Non uso HTML, PHP o CSS da anni e non sono mai stato bravo con loro per cominciare. C'è molto qui, quindi non mi aspetto recensioni super approfondite, ma qualsiasi cosa sarebbe apprezzata.

Nello specifico però:

  • La funzione new_color_f()è un disastro. Fondamentalmente, voglio essere in grado di produrre una funzione che ha alcuni parametri già impostati, e lo sto facendo chiudendo i parametri della funzione che lo racchiude. È lungo e brutto, però, ed è peggiorato dalla usesintassi di PHP . Eventuali suggerimenti sarebbero apprezzati.

  • Anche la funzione defaulting_get()sembra un odore. Devo tenere conto dei dati GET mancanti e potenzialmente vuoti. Ciò ha portato a utilizzare un bizzarro mix di operatori ??e ?:.

Qualsiasi cosa tranne:

  • Sì, è stupido farlo in PHP come sono io, ed è anche stupido farlo usando un HTML table. Causa problemi con il modulo di input che viene cancellato ogni voce e causa anche un incredibile ritardo del browser quando si ricevono tabelle più grandi. Volevo fare un po 'di esperienza con il pensiero PHP prima di provare qualcosa di più complicato, e ho sempre amato questo progetto. In realtà, dovrei farlo in JavaScript o utilizzare AJAX per richiedere immagini invece di utilizzare un modulo.

Il server esegue PHP 7.4 come riferimento.


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;
    }

Risposte

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

Interfaccia utente

Ho notato che se inserivo dei valori nel modulo e poi lo inviavo, i valori non venivano mantenuti dopo il caricamento della pagina successivo. Sarebbe bello che i valori persistessero negli input del modulo. Questo è facile da ottenere poiché il modulo è reso da una pagina PHP.

Poiché il codice PHP verso la seconda metà di index.php arriva dopo la chiusura dell'HTML, l'HTML generato per la tabella è tecnicamente al di fuori del documento HTML:

    </form>

  </body>
</html>

<table class='display_table'>

La maggior parte dei browser moderni visualizzerebbe la tabella come se fosse all'interno del <body>tag, ma tecnicamente non è una struttura valida. Utilizzandohttps://validator.w3.org/ con la sorgente generata mostra questo output:

Una convenzione comune è quella di avere la maggior parte del codice PHP all'inizio del file, particolarmente utile se è presente una funzionalità che modifica le intestazioni HTTP, ad esempio le chiamate a header()poiché le intestazioni non possono essere inviate dopo che l'HTML è stato emesso.

Ricorda che header()deve essere chiamato prima che venga inviato qualsiasi output effettivo, tramite normali tag HTML, righe vuote in un file o da PHP. 1

L'HTML generato dalle chiamate PHP (ad esempio le chiamate a emit_mandelbrot_view()cui sarebbe necessario returnl'HTML generato invece di chiamare echodirettamente) può essere memorizzato in una variabile o emesso in linea, opzionalmente utilizzando la sintassi di scelta rapida per echo- es <?=:

<?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>

Suggerimenti sul codice

  • new_color_fè un disastro. Fondamentalmente, voglio essere in grado di produrre una funzione che ha alcuni parametri già impostati, e lo sto facendo chiudendo i parametri della funzione che lo racchiude. È lungo e brutto però, ed è peggiorato dalla sintassi d'uso di PHP. Eventuali suggerimenti sarebbero apprezzati.

Poiché hai confermato che il server su cui gira il codice utilizza PHP 7.4, il codice può essere aggiornato per sfruttare le funzionalità più recenti come le funzioni delle frecce

Le funzioni freccia supportano le stesse funzionalità delle funzioni anonime, tranne per il fatto che l'utilizzo di variabili dall'ambito padre è sempre automatico.

Ciò consentirebbe usedi eliminare la dichiarazione in new_color_f().


La funzione range()può essere utilizzata con foreachper semplificare i cicli annidati all'interno di 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) {

Può essere semplificato in:

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

anche se data la quantità di volte che viene chiamata quella funzione, potrebbe non essere consigliabile aggiungere più chiamate di funzione solo per semplificare la sintassi.


Per questo blocco in 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),
    );

Ovviamente ci sono molte duplicazioni qui. Una semplificazione sarebbe quella di inserire le chiavi della stringa di query in un array e di passarci sopra, passandole alla funzione e memorizzando i risultati in un array che può essere distribuito in una chiamata a new_color_f().


In test_point()l'iteratore viene allestito all'esterno del ciclo:

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

A differenza del codice c, il codice $ipuò essere dichiarato nella prima istruzione del forciclo ed essere comunque visibile al di fuori del ciclo poiché le variabili non sono limitate a un blocco ma piuttosto a una funzione (se applicabile). Questo non è realmente spiegato bene nello scope della variabile della documentazione PHP sebbene ci sia un commento a riguardo .

dodothedreamer su gmail dot com ¶ 8 anni fa


Nota che a differenza di Java e C ++, le variabili dichiarate all'interno di blocchi come i loop o gli if, saranno riconosciute e accessibili anche al di fuori del blocco, quindi:

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

Stamperebbe 4.


Questa definizione costante sembra un'ottimizzazione in stile C.

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

Correggimi se sbaglio ma dubito che ci sia un'ottimizzazione nel dichiararlo come 255:

const COLOR_MAX = 255;

PS Ho chiesto di quella dichiarazione costante su Stack Overflow . Dalle risposte finora sembra essere solo una preferenza personale per quanto riguarda il modo in cui viene dichiarato il valore. Se preferisci utilizzare la sintassi spostata di bit, sentiti libero di farlo anche se tieni presente che se lavorassi in un team con altri, ciò potrebbe sorgere come una domanda in una revisione del codice.

1 bdsl Sep 13 2020 at 22:55

Tipi

Rende il codice più facile da capire se si specificano i tipi per tutte le firme di funzione. PHP controllerà i tipi se specificato in fase di esecuzione ogni volta che si entra e si esce da una funzione e durante la scrittura in una proprietà di classe, aiutandoti a rilevare e trovare errori molto più rapidamente, specialmente se si utilizza un IDE o uno strumento di analisi statica. Aiutano anche a comunicare l'intento del codice per semplificarne l'aggiunta.

Alcuni esempi di modifiche per aggiungere tipi:

In index.php, cambia infunction defaulting_get($key, $default) {function defaulting_get(string $key, int $default): int {

In display.php, cambia function clamp_color($n) {infunction clamp_color(int $n): int {

e

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

per

    /** @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 {

Non sono sicuro di avere esattamente il tipo di chiusura nel docblock.

Lo farei per ogni singola funzione.

Evitando use

Puoi evitare la necessità usedi sfruttare la sintassi di chiusura breve introdotta in 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 racchiude automaticamente tutte le variabili menzionate dopo la =>chiusura breve.

Classi e caricamento automatico

Il moderno PHP idiomatico, in particolare per le applicazioni più grandi, tende ad avere quasi tutto il codice sotto forma di classi, anche se l'applicazione non è concepita per essere orientata agli oggetti. Un vantaggio dell'utilizzo delle classi è che il sistema di caricamento automatico di PHP può caricarle su richiesta. Questo non è disponibile per le funzioni gratuite. Le funzioni statiche sulle classi possono essere convenientemente utilizzate al posto delle funzioni libere.

La sostituzione di tutte le funzioni libere con funzioni di classe statica renderà anche possibile contrassegnare tutte le funzioni utilizzate solo all'interno della classe che le dichiara come private, il che dovrebbe rendere molto più semplice la comprensione del flusso di codice.

Il modo tipico per usare il caricamento automatico è impostare il progetto con Composer, organizzare le classi una per file, seguendo le regole PSR-4 per la posizione e il nome di ogni file e fare in modo che Composer generi un file autoload.php . Questo può essere chiamato con require_onceda index.phpo qualsiasi altro punto di ingresso. Eventuali classi necessarie successivamente verranno caricate automaticamente se possono essere trovate.

Vorrei anche fare in modo che new_color_f restituisse un oggetto invece di una chiusura e lo rinominerei di conseguenza. Questo oggetto può avere campi privati ​​invece di variabili racchiuse e una getRGBfunzione pubblica.