Przyśpieszyć Zend_Captcha_Image

by Mateusz Tymek — on Zend Framework, PHP

Head's up! This post was written back in 2009 and is very likely to contain outdated information.

Ogromną wadą komponentu Zend_Captcha_Image jest jego niska wydajność. Główną przyczyną jest użycie biblioteki GD i czystego PHP do utworzenia i zdeformowania obrazka. Jak obejść ten problem?

Interesującym rozwiązaniem będzie zastosowanie ImageMagick. Rozszerzenie to dostarcza zestaw klas manipulujących obrazkami, a bogaty zestaw funkcji pozwoli na uzyskanie efektu podobnego do Zend_Captcha_Image. Ta ostatnia klasa zostanie wykorzystana jako baza, co bardzo ułatwi proces kodowania.

Oto podstawowa klasa:

<?php

class My_Captcha_Imagick extends Zend_Captcha_Image
{
    /**
     * Generate image captcha
     *
     * @param string $id Captcha ID
     * @param string $word Captcha word
     */
    protected function _generateImage($id, $word)
    {
        // jeśli brakuje rozszerzenia imagick, to obrazek
        // jest generowany zwykłą metodą - przy pomocy biblioteki GD
        if (!extension_loaded("imagick")) {
            return parent::_generateImage($id, $word);
        }

        $font = $this->getFont();

        if (empty($font)) {
            require_once 'Zend/Captcha/Exception.php';
            throw new Zend_Captcha_Exception("Image CAPTCHA requires font");
        }

        $w     = $this->getWidth();
        $h     = $this->getHeight();
        $fsize = $this->getFontSize();

        $img_file   = $this->getImgDir() . $id . $this->getSuffix();

        if(empty($this->_startImage)) {
            $img = new Imagick();
            $img->newImage($w, $h, new ImagickPixel('#FFFFFF'), 'png');
        } else {
            $img = new Imagick($this->_startImage);
            $w = $img->getImageWidth();
            $h = $img->getImageHeight();
        }

        $text = new ImagickDraw();
        $text->setFilLColor('#000000');
        $text->setFont($font);
        $text->setFontSize($h - 10);
        $text->setGravity(Imagick::GRAVITY_CENTER);
        $text->annotation(0, 0, $word);

        // generate noise
        $noise = new ImagickDraw();
        $noise->setFilLColor('#000000');
        for ($i=0; $i<$this->_dotNoiseLevel; $i++) {
            $x = mt_rand(0,$w);
            $y = mt_rand(0,$h);
            $noise->circle($x, $y, $x+mt_rand(0.3, 1.7), $y+mt_rand(0.3, 1.7));
        }
        for($i=0; $i<$this->_lineNoiseLevel; $i++) {
            $noise->line(mt_rand(0,$w), mt_rand(0,$h), mt_rand(0,$w), mt_rand(0,$h));
        }
        $img->drawImage($text);
        $img->waveImage(5, mt_rand(60, 100));
        $img->drawImage($noise);
        $img->swirlImage(mt_rand(10, 30));

        file_put_contents($img_file, $img);
        unset($img);
    }
}

jedyna metoda, _generateImage(), jest odpowiednio przerobioną wersją z klasy bazowej. Można poeksperymentować z argumentami metod waveImage() i swirtlImage(), ewentualnie dodać własne filtry dla uzyskania ciekawszego efektu.

Naszą nową klasę można stosować dokładnie tak samo jak Zend_Captcha_Image. Przykład - zastosowanie jej w formularzu:

$this->addElement('captcha', 'captcha', array(
            'label' => 'Przepisz tekst z obrazka:',
            'ignore' => true,
            'captcha' => new My_Captcha_Imagick(array(
                'font'    => APPLICATION_PATH . '/../data/fonts/arialcebold.ttf',
                'wordLen' => 6,
                'timeout' => 300
            ))
        ));

A co z wydajnością? Sam proces generowania obrazka jest ok. 10 razy szybszy niż w rozwiązaniu Zenda. Po dodaniu narzutu związanego z zapisem do pliku stosunek ten będzie wynosił ok. 1:4.


comments powered by Disqus