ugrás a tartalomhoz

Áttetsző képek kezelése PHP alatt

Poetro · 2012. Feb. 14. (K), 10.30

Aki már dolgozott PHP alatt áttetsző képekkel (azaz olyan képekkel, amelyek részben átlátszóak), az tudhatja, hogy a beépített GD rendszer nem igazán segíti munkáját. A GD2 bevezetésével ugyan javultak a dolgok, de messze van még a rendszer attól, hogy igazán jól használható legyen áttetsző képek kezelésében.

Az odáig rendben van, hogy nagyszerűen kezelhetünk benne nem áttetsző képeket, erre minden lehetőségünk megvan. Ugyanakkor áttetsző képek esetén a következő problémákba ütközünk:

  1. Ha a képünk áttetsző, akkor a rendszer hajlamos eldobni az átlátszóságot a pixelekről, azaz pixelenként csak 24 bitet használ.
  2. Nincs támogatás arra, hogy egy már áttetsző képnek módosítsuk az átlátszóságát.

Áttetsző képek esetén a legfontosabb, hogy egy olyan üres vászonnal (képpel) kezdjünk a munkát, ami true color, és csak átlátszó pixeleket tartalmaz. Ehhez írtam egy egyszerű, de nagyszerű függvényt, ami ezt szépen megvalósítja:

/**
 * Create a new true color image with solid color background.
 *
 * @param $width
 *   The image's width.
 * @param $height
 *   The image's height.
 * @param $color
 *   24 bit color of the background.
 * @param $opacity
 *   Opacity of the background from 0 to 100 where 100 means totally opaque
 *   and 0 is transparent.
 * @return
 *   An image link resource.
 */
function image_generate($width, $height, $color = 0, $opacity = 100) {
    $im = imagecreatetruecolor($width, $height);
    
    // Handle transparency.
    imagealphablending($im, true);
    imagesavealpha($im, true);
    
    // Generate RGBA components for the color.
    $r = ($color >> 16) & 0xFF;
    $g = ($color >>  8) & 0xFF;
    $b =  $color        & 0xFF;
    
    // $a is between 0 - 127 where 0 is totally opaque and 127 is transparent.
    $a = 127 - round(127 * $opacity / 100);
    
    if ($a) {
        $fill = imagecolorallocatealpha($im, $r, $g, $b, $a);
    } else {
        $fill = imagecolorallocate($im, $r, $g, $b);
    }
    
    // Fill the background, and prepare for drawing.
    imagefill($im, 0, 0, $fill);
    imageantialias($im, true);
    
    return $im;
}

Az image_generate() függvény az imagecreatetruecolor() függvény segítségével létrehoz egy true color képet, majd azt kiszínezi a megfelelő színűre. Amennyiben az $opacity értéknek 0-t adunk meg, akkor teljesen átlátszó képet kapunk, ekkor a szín mellékessé válik. Vagyis csak válna, ugyanis például az imagecopymerge() függvény, ha megadunk neki 0 és 127 közötti $ptc értéket (azaz átlátszóságot), akkor, mivel ez a függvény eldobja a kép eredeti áttetszőségre vonatkozó információit, ezzel a teljesen átlátszatlan színnel fogja másolni a képet. Azaz, ha az áttetsző színünk, amivel a vásznat kiszíneztük fekete volt, akkor az átlátszatlan feketévé, ha fehér, akkor fehérré válik.

A függvény még beállít pár lényeges információt a képpel kapcsolatban. Ezek pedig a következők:

  • A kép tartalmazzon áttetszőséget.
  • Tartsa meg az áttetszőségre vonatkozó információt mentéskor.
  • A rajta végzett műveletekkor az anti-alias függvényeket is használja (átméretezéskor és áttetsző képek egymásra másolásakor van jelentősége).

Ahhoz, hogy egy egy képnek megváltoztassuk az átlátszóságát, két dolgot kell tennünk. Mivel az imagecopymerge(), amivel elméletileg az áttetszőséget is tudjuk módosítani (a $ptc paraméter megadásával), nem működik már korábban is áttetsző képeken, ezért létre kell hozni egy átlátszó vásznat (például az image_generate() függvény segítségével), majd erre kell pixelenként átmásolni az képünk adatait, miközben azoknak megváltoztatjuk az átlátszóságát. Erre jött létre az imagecopy_opacity() függvény:

/**
 * Copy an image to destination with changing it's opacity.
 *
 * @param $dst_im
 *   Destination image link resource.
 * @param $src_im
 *   Source image link resource.
 * @param $dst_x
 *   x-coordinate of destination point.
 * @param $dst_y
 *   y-coordinate of destination point.
 * @param $src_x
 *   x-coordinate of source point.
 * @param $src_y
 *   y-coordinate of source point.
 * @param $src_w
 *   Source width.
 * @param $src_h
 *   Source height.
 * @param $opacity
 *   Opacity from 0 to 100 where 100 means totally opaque 0 is transparent.
 */
function imagecopy_opacity($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity) {
    // Calculate the width/height to be copied based on the source width/height,
    // the destination width/height, and the specified width/height, to only copy
    // available pixels to available destinations.
    
    $width  = min($src_w, imagesx($src_im), imagesx($dst_im) - $dst_x);
    $height = min($src_h, imagesy($src_im), imagesy($dst_im) - $dst_y);
    
    $alpha  = $opacity / 100;
    
    for ($x = 0; $x < $width; $x++) {
        for ($y = 0; $y < $height; $y++) {
            // Get color at (x, y) pixel on the source, and change it's opacity.
            $color = imagecolorat($src_im, $x + $src_x, $y + $src_y);
            
            $r = ($color >> 16) & 0xFF;
            $g = ($color >>  8) & 0xFF;
            $b =  $color        & 0xFF;
            
            // $a is between 0 - 127 where 0 is totally opaque and 127 is transparent.
            $a = 127 - round((127 - (($color >> 24) & 0x7F)) * $alpha);
            
            $newcolor = imagecolorexactalpha($dst_im, $r, $g, $b, $a);
            
            // Set the color at the same pixel on destination.
            imagesetpixel($dst_im, $x + $dst_x, $y + $dst_y, $newcolor);
        }
    }
}

A függvénynek meg kell adni a cél és a forrás képet, valamint az eltolást ezeken a képeken, a forrás kép másolandó darabját, valamint természetesen az átlátszóságot. A PHP átlátszóságkezelése egy kicsit fura ezen a téren. Ugyanis a dokumentáció nem írja, de az imagecolorat() függvény a színen kívül visszaadja az átlátszóságot is a 24. bit feletti információban 7 bites pontossággal, azaz 0–127-es skálán, ahol a 0 átlátszatlant, 127 pedig teljesen átlátszót jelent.

Azaz a függvényünk nem tesz mást, mint a megadott régióban végighalad a forráskép összes pixelén, azoknak kiszedi a színinformációját (az átlátszósággal egyetemben), majd az átlátszóságot módosítva felülírja a célképen a megadott pixeleket. Ekkor ugye a régióba eső összes pixel felülírásra kerül, de az eredeti imagecopy() esetében is pontosan ugyanezt a működést kapjuk. Ezek után, ha ezt a módosított képet használni akarjuk, nem kell mást tennünk, mint az imagecopymerge() használatával (a $ptc értéket mellőzve) rámásoljuk arra a képre, amit szeretnénk.

Ahhoz, hogy PHP-ban áttetsző képpel tudjunk dolgozni a következőket kell tennünk tehát:

  1. Betöltjük a használni kívánt forrás képet az imagecreatefrom*() segítségével.
  2. Létrehozunk egy teljesen átlátszó képet (például az image_generate() függvény segítségével).
  3. Rámásoljuk forrás képet az átlátszó képre (imagecopy(), imagecopymerge(), imagecopyresampled(), illetve imagecopy_opacity()).
  4. Elkezdünk vele dolgozni, mint bármilyen más képpel.
 
1

Miért imagecopymerge?

Gixx · 2012. Feb. 14. (K), 10.36
Az imagecopyresapled-del semmi bajom nem szokott lenni, minden gond nélkül tudok áttetsző képeket kezelni vele...
2

Összefoglaló

Poetro · 2012. Feb. 14. (K), 10.43
Amint az összefoglalóban is írtam, ha megfelelően előkészítettük a vásznunkat, már (szinte) teljesen lényegtelen, melyik másoló metódust használjuk, mindnek megfelelően működnie kell (kivéve a cikkben említett problémát).
3

Thx

Blintux · 2012. Feb. 14. (K), 12.30
Köszi a bejegyzést és a függvényeket, nagyon hasznos! :)
4

Köszönjük

Hidvégi Gábor · 2012. Feb. 14. (K), 13.14
Hasznos bejegyzés, annyit tennék hozzá, hogy a képműveletek nagy méretnél vagy több fájlnál elég processzorigényesek tudnak lenni, ezért érdemes néha külön szálon elvégezni őket, PHP-ban például az exec parancs segítségével meg tudunk hívni egy másik scriptet a háttérben, például (linuxon/unixon, kulcs az & jel):
exec('/usr/local/php/bin/php /home/user/script.php &');
5

(linuxon/unixon, kulcs az &

kuka · 2012. Feb. 14. (K), 13.29
(linuxon/unixon, kulcs az & jel):
Illetve a linkelt dokumentációbeli megjegyzés, mely elárulja, hogy a & önmagában kevés a valós háttérben futtatáshoz:
Note:

If a program is started with this function, in order for it to continue running in the background, the output of the program must be redirected to a file or another output stream. Failing to do so will cause PHP to hang until the execution of the program ends.
Azaz az általad írt kód esetében a szülő procesz ugyanúgy be fogja várni az exec() lefutását, mintha a & ott sem volna.
6

Köszönöm a kiegészítést

Hidvégi Gábor · 2012. Feb. 14. (K), 13.35
Tehát helyesen a kód:
exec('/usr/local/php/bin/php /home/user/script.php > /home/user/akarmi.txt &');
7

Nem csak processzor, hanem

inf3rno · 2012. Feb. 14. (K), 17.11
Nem csak processzor, hanem memória igényesek is, érdemes mindenhol destroy-al eltakarítani a már nem szükséges képeket, mert hamar megtelik a memória. (Én sprite készítésnél futottam bele ilyenbe.)
8

Szerintem erről a témáról

Hidvégi Gábor · 2012. Feb. 14. (K), 17.27
Szerintem erről a témáról érdemes lenne egy ilyen összesített blogbejegyzést vagy cikket (how to) írni.