PHP Mandelbrot Set Generator

Sep 04 2020

Web programlamaya aşina olmam gerekiyor, bu yüzden GET isteklerini kabul eden ve Mandelbrot setinin bir görüntüsünü renkli hücreler tablosu olarak sunan oldukça basit bir PHP sayfası yazmaya karar verdim.

Örnekler:

ve

Yanıtı tablo olarak gönderiyorum çünkü henüz görüntü kitaplıklarını kullanmak istemedim, bu yüzden bunu tamamen HTML kullanarak yapmayı denemek istedim ve daha basit bir yol düşünemedim.

Temel olarak nasıl çalıştığını, "Sınırlar" alanlarını, görünüm ve tablonun boyutunu, en boy oranını (ondalık olarak) ve ardından görüntünün nasıl olduğuna karar veren dokuz renkli alanı belirterek Kümenin neresinde görüntülemek istediğinizi belirtirsiniz. renkli olacak.

Ana mantık, aslında geçen yılki C kodumun bir bağlantı noktası ve boyama fikri, birkaç yıl önce Clojure'da yaptığım bir projeden alınmıştır .

Herhangi bir konuda öneri arıyorum. Yıllardır HTML, PHP veya CSS kullanmadım ve onlarla başlamak için asla harika değildim. Burada çok şey var, bu yüzden süper kapsamlı incelemeler beklemiyorum, ancak her şey takdir edilecektir.

Özellikle de:

  • İşlev new_color_f()bir felakettir. Temel olarak, önceden ayarlanmış bazı parametreleri olan bir fonksiyon üretebilmek istiyorum ve bunu çevreleyen fonksiyonun parametrelerini kapatarak yapıyorum. Yine de uzun ve çirkin ve PHP'nin usesözdizimi tarafından daha da kötüleştiriliyor . Herhangi bir öneri memnuniyetle karşılanacaktır.

  • Fonksiyon defaulting_get()da bir koku gibi görünüyor. GET verilerinin eksik olduğunu ve potansiyel olarak boş olduğunu hesaba katmam gerekiyor. Bu, tuhaf bir karışım ??ve ?:operatörlerin kullanılmasına yol açar .

Aşağıdakiler dışında herhangi bir şey:

  • Evet, benim yaptığım gibi PHP'de bunu yapmak aptalca ve bunu bir HTML kullanarak yapmak da aptalca table. Her girişin silinmesi giriş formu ile ilgili sorunlara neden olur ve ayrıca daha büyük tablolar alırken inanılmaz tarayıcı gecikmesine neden olur. Daha karmaşık bir şey denemeden önce PHP ile ilgili biraz deneyim kazanmak istedim ve bu projeyi her zaman sevmişimdir. Gerçekten, bunu JavaScript'te yapmalıyım veya form kullanmak yerine resim istemek için AJAX kullanmalıyım.

Sunucu referans için PHP 7.4 çalıştırıyor.


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

Yanıtlar

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

Kullanıcı arayüzü

Forma değerler girip daha sonra gönderirsem, değerlerin bir sonraki sayfa yüklemesinden sonra kalıcı olmadığını fark ettim. Değerlerin form girdilerinde kalması güzel olurdu. Form bir PHP sayfası tarafından oluşturulduğu için bunu başarmak kolaydır.

İndex.php'nin ikinci yarısına yönelik PHP kodu HTML kapatıldıktan sonra geldiğinden, tablo için oluşturulan HTML teknik olarak HTML belgesinin dışındadır:

    </form>

  </body>
</html>

<table class='display_table'>

Çoğu modern tarayıcı, tabloyu <body>etiketin içindeymiş gibi görüntülerdi, ancak teknik olarak geçerli bir yapı değildir. Kullanmahttps://validator.w3.org/ oluşturulan kaynak ile bu çıktıyı gösterir:

Yaygın bir kural, PHP kodunun çoğunun dosyanın en üstünde olmasıdır - özellikle HTTP üstbilgilerini değiştiren işlevler varsa, örneğin header(), HTML gönderildikten sonra başlıklar gönderilemediğinden, çağrılar varsa yararlıdır .

Bunun header(), herhangi bir gerçek çıktı gönderilmeden önce, normal HTML etiketleri, bir dosyadaki boş satırlar veya PHP'den çağrılması gerektiğini unutmayın . 1

PHP çağrılarından oluşturulan HTML (örneğin , doğrudan çağrı yapmak yerine üretilen HTML'ye emit_mandelbrot_view()ihtiyaç duyan çağrılar ), isteğe bağlı olarak kısayol sözdizimi kullanılarak bir değişken içinde saklanabilir veya satır içi yayınlanabilir - örn :returnechoecho<?=

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

Kod önerileri

  • new_color_fbir felakettir. Temel olarak, önceden ayarlanmış bazı parametreleri olan bir fonksiyon üretebilmek istiyorum ve bunu çevreleyen fonksiyonun parametrelerini kapatarak yapıyorum. Yine de uzun ve çirkin ve PHP'nin kullanım sözdizimi tarafından daha da kötüleştiriliyor. Herhangi bir öneri memnuniyetle karşılanacaktır.

Kodun üzerinde çalıştığı sunucunun PHP 7.4 kullandığını onayladığınızdan, kod ok işlevleri gibi daha yeni özelliklerden yararlanmak için güncellenebilir.

Ok işlevleri, anonim işlevlerle aynı özellikleri destekler, ancak ana kapsamdaki değişkenlerin kullanılması her zaman otomatiktir.

Bu, useifadenin ortadan kaldırılmasına izin verir new_color_f().


İşlev range(), aşağıdaki foreachiç içe geçmiş döngüleri basitleştirmek için birlikte kullanılabilir 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) {

Aşağıdakilere basitleştirilebilir:

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

ancak bu işlevin çağrılma sayısı göz önüne alındığında, sözdizimini basitleştirmek için daha fazla işlev çağrısı eklemeniz tavsiye edilmeyebilir.


İndex.php'deki bu blok için:

    $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),
    );

Açıkçası burada çok fazla kopya var. Bir basitleştirme, sorgu dizesi anahtarlarını bir diziye koymak ve bunların üzerinden döngü yapmak, onları işleve geçirmek ve sonuçları bir çağrıya yayılabilecek bir dizide depolamak olabilir new_color_f().


Gelen test_point()yineleyici döngü dışında kurulur:

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

C'nin aksine kod $i, fordöngünün ilk ifadesinde bildirilebilir ve yine de döngünün dışında görülebilir çünkü değişkenler bir blokla değil, bir işlevle (varsa) sınırlandırılır. Bu, hakkında bir yorum olmasına rağmen , PHP dokümantasyon değişken kapsamında gerçekten iyi açıklanmamıştır .

dodothedreamer at gmail dot com ¶ 8 yıl önce


Java ve C ++ 'dan farklı olarak, döngüler veya eğer'ler gibi bloklar içinde bildirilen değişkenlerin de blok dışında tanınacağını ve erişilebilir olacağını unutmayın, bu nedenle:

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

4 yazdırır.


Bu sabit tanım, bir C stili optimizasyonuna benziyor

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

Yanılıyorsam düzeltin ama bunu şu şekilde ilan etmekten daha fazla optimizasyon olduğundan şüpheliyim 255:

const COLOR_MAX = 255;

Not : Stack Overflow'daki bu sabit bildirimi sordum . Şimdiye kadar verilen yanıtlardan, değerin nasıl beyan edildiği konusunda kişisel tercih gibi görünüyor. Bit kaydırmalı sözdizimini kullanmayı tercih ediyorsanız, bunu yapmaktan çekinmeyin, ancak başkalarıyla bir ekip üzerinde çalışacaksanız, bir kod incelemesinde soru olarak ortaya çıkabilecek bir şey olduğunu önceden unutmayın.

1 bdsl Sep 13 2020 at 22:55

Türler

Tüm işlev imzaları için türler belirtirseniz kodun anlaşılmasını kolaylaştırır. PHP, bir işleve her girip çıkarken ve bir sınıf özelliğine yazarken çalışma zamanında belirtilmişse türleri kontrol ederek, özellikle bir IDE veya statik analiz aracı kullanıyorsanız hataları çok daha hızlı bir şekilde saptamanıza ve bulmanıza yardımcı olur. Ayrıca, kodunuza daha fazlasını eklemeyi kolaylaştırmak için kodunuzun amacını iletmeye yardımcı olurlar.

Tür eklemek için bazı örnek değişiklikler:

İndex.php'de şu şekilde değiştirin :function defaulting_get($key, $default) {function defaulting_get(string $key, int $default): int {

Display.php'de şu şekilde değiştirin function clamp_color($n) {:function clamp_color(int $n): int {

ve

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

-e

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

Bu kapatma türünü tam olarak docblock'ta bulduğumdan emin değilim.

Bunu her işlev için yapardım.

Kaçınma use

usePHP 7.4'te sunulan kısa kapanış sözdiziminden yararlanarak bu ihtiyacı önleyebilirsiniz :

        /** @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 =>, kısa kapanışta belirtilen tüm değişkenleri otomatik olarak ekler .

Sınıflar ve otomatik yükleme

Modern deyimsel PHP, özellikle daha büyük uygulamalar için, uygulamanın nesne yönelimli olması amaçlanmasa bile, neredeyse tüm kodların sınıflar biçiminde olma eğilimindedir. Sınıfları kullanmanın bir avantajı, PHP'nin otomatik yükleme sisteminin bunları isteğe bağlı olarak yükleyebilmesidir. Bu, ücretsiz işlevler için mevcut değildir. Sınıflar üzerindeki statik işlevler, serbest işlevlerin yerine rahatlıkla kullanılabilir.

Tüm serbest işlevlerin statik sınıf işlevleriyle değiştirilmesi, yalnızca bunları olarak bildiren sınıf içinde kullanılan işlevlerin işaretlenmesini de mümkün kılacaktır, bu da privatekod akışının anlaşılmasını çok daha kolay hale getirmelidir.

Otomatik yüklemeyi kullanmanın tipik yolu, projeyi Composer ile kurmak, sınıfları dosya başına bir tane düzenlemek, her dosyanın konumu ve adı için PSR-4 kurallarını takip etmek ve Composer'ın bir autoload.php dosyası oluşturmasını sağlamaktır . Bu ile çağrılabilir require_oncegelen index.phpveya başka herhangi bir giriş noktasına. Bundan sonra ihtiyaç duyulan tüm sınıflar, bulunabilirlerse otomatik olarak yüklenecektir.

Ayrıca new_color_f'nin bir kapanma yerine bir nesne döndürmesini ve buna göre yeniden adlandırmasını sağlardım. Bu nesnenin, kapalı değişkenler yerine özel alanları ve bir getRGBgenel işlevi olabilir.