How to solve a white screen problem in the GD library?

I have a problem in the GD PHP library. When I send an image to a localhost VM the image is normally generated on the screen, but when I try to do it on a VPS I do not generate the image and the screen is white. I have already checked if the lib GD is the same on both machines and everything seems to be okay.

The following is the code that I’m generating the image on the screen:

    $image = imagecreatefrompng((isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]" . "/wp-content/uploads/cart1.png");

    $imageCart = imagecreatefrompng($perfil);

    $titleColor = imagecolorallocate($image, 255, 255, 255);
    $gray = imagecolorallocate($image, 100, 100, 100);
    $black = imagecolorallocate($image, 0, 0, 0);

    imagecopymerge($image, $imageCart, 60, 177, 0, 0, imagesx($imageCart), imagesy($imageCart), 100);

    imagestring($image, 5, 170, 175, "NOME  " . strtoupper($nomeCompleto), $titleColor);
    imagestring($image, 5, 170, 205, "CPF  " . $cpf, $titleColor);
    imagestring($image, 5, 170, 235, "MATRICULA  " . $matricula, $titleColor);
    imagestring($image, 5, 170, 263, "DT NASCIMENTO " . $dtnacimento, $titleColor);

    header("Content-type: image/png");

    imagepng($image);
    imagepng($imageCart);

    imagedestroy($image);
    imagedestroy($imageCart);

Answer

This problem was interesting, since the local and remote server environments were reported in the comments as being the same. The usual problem is that the gd extension is installed in one, and missing in the other, but that was not the case here. They were even reported as being of the same internal version number.

So the first problem is here:

imagepng($image);
imagepng($imageCart);

The imagepng() function takes the binary data of an image and sends it to /dev/stdout, where it will be captured by the web server, and transmitted to the requesting client. Having two of these could make for an odd result, since the browser will see two concatenated PNG images. As mentioned in the comments, this might work for some browsers, and might not for others – in other words we can’t rely on it working. It is best to send only valid output, even if we can get it to work in some cases (where it worked, the browser probably used the first one and discarded the second). So the first thing to do was to only send one image.

The next problem was in this line, found by debugging:

$image = imagecreatefrompng((isset($_SERVER['HTTPS']) ? "https" : "http") .
    "://$_SERVER[HTTP_HOST]" .
    "/wp-content/uploads/cart1.png");

Essentially, this loads a source PNG image via HTTP/HTTPS. This takes advantage of a feature in PHP that allows URLs to be used in place of file paths. From the manual:

A URL can be used as a filename with this function if the fopen wrappers have been enabled. See fopen() for more details on how to specify the filename. See the Supported Protocols and Wrappers for links to information about what abilities the various wrappers have, notes on their usage, and information on any predefined variables they may provide.

I would hazard a guess then that your allow_url_fopen is true on your local machine, and false or not set on your VPS. This could have been made to work by changing it remotely, but the fix that was made was probably better – and that is to load it from the file system directly.

If you use the fopen wrapper, then what you are actually doing is to make a request of the web server, which fetches the image from disk, and then returns it via curl. So by fetching the image directly using its filename, you are “cutting out the middleman” and getting it more efficiently.

It is normal to use the directory traversal operator in these cases, to create a pathname that is relative to the script’s directory. A perfectly good solution, which was used, is:

imagecreatefrompng("../../../wp-content/uploads/cart1.png")

You can also use a magic variable (assuming the script is three levels down from the project root):

$projectDir = realpath(__DIR__ . "/../../..");
imagecreatefrompng($projectDir . "/wp-content/uploads/cart1.png")

You will probably find that WordPress sets some of its own project directory constants, so they could have been used in this case too.