Problem with resizing PNG images in PHP

Asked

Viewed 682 times

1

Good night.

I’m with a virtual store script here, but I’m having problems with PNG images.

When uploading an image like this: inserir a descrição da imagem aqui .

The script checks the extension based on mimetype, "sanitize" the file name and after all, generates 3 sizes of this image, being Standard, Thumbnail and Zoom. Being that in the images "Standard" and "Thumbnail" it makes the resizing and in Zoom, if the image is big enough it keeps the same size.

The problem is that when it resizes to a smaller size, the image gets kind of "broken":

Imagem do tamanho "Padrão"

And in miniature it gets even worse. How can I make this image smoother?

The upload and processing script of this image is a bit complex, so I will post here the excerpts that I think are the main.

The script below uploads the image:

public static function importImage($temporaryPath, $originalFilename, $productId, $hash = false, $moveTemporaryFile = true, $generateImages = true)
{
    if (!file_exists($temporaryPath)) {
        throw new ISC_PRODUCT_IMAGE_SOURCEFILEDOESNTEXIST_EXCEPTION($temporaryPath);
    }

    try {
        $library = ISC_IMAGE_LIBRARY_FACTORY::getImageLibraryInstance($temporaryPath);
    } catch (ISC_IMAGE_LIBRARY_FACTORY_INVALIDIMAGEFILE_EXCEPTION $ex) {
        throw new ISC_PRODUCT_IMAGE_IMPORT_INVALIDIMAGEFILE_EXCEPTION();
    } catch (ISC_IMAGE_LIBRARY_FACTORY_NOPHPSUPPORT_EXCEPTION $ex) {
        throw new ISC_PRODUCT_IMAGE_IMPORT_NOPHPSUPPORT_EXCEPTION();
    }

    if ($library->getWidth() < 1 || $library->getHeight() < 1) {
        throw new ISC_PRODUCT_IMAGE_IMPORT_EMPTYIMAGE_EXCEPTION();
    }

    $finalName = $originalFilename;


    $finalName = basename($finalName); // remove any path components from the filename
    $finalName = self::sanitiseFilename($finalName);

    if (!self::isValidFilename($finalName, false)) {
        throw new ISC_PRODUCT_IMAGE_IMPORT_INVALIDFILENAME_EXCEPTION($finalName);
    }

    // correct the uploaded extension
    $correctExtension = $library->getImageTypeExtension(false);
    if (strtolower(pathinfo($finalName, PATHINFO_EXTENSION)) != $correctExtension) {
        // remove existing extension and trailing . if any
        $finalName = preg_replace('#\.[^\.]*$#', '', $finalName);
        // add correct extension
        $finalName .= '.' . $correctExtension;
    }

    // generate a path for storing in the product_images directory
    $finalRelativePath = self::generateSourceImageRelativeFilePath($finalName);

    $image = new ISC_PRODUCT_IMAGE();
    $image->setSourceFilePath($finalRelativePath);

    $finalAbsolutePath = $image->getAbsoluteSourceFilePath();
    $finalDirectory = dirname($finalAbsolutePath);

    if (!file_exists($finalDirectory)) {
        if (!isc_mkdir($finalDirectory, ISC_WRITEABLE_DIR_PERM, true)) {
            throw new ISC_PRODUCT_IMAGE_IMPORT_CANTCREATEDIR_EXCEPTION($finalDirectory);
        }
    }

    if ($moveTemporaryFile) {
        if (!@rename($temporaryPath, $finalAbsolutePath)) {
            throw new ISC_PRODUCT_IMAGE_IMPORT_CANTMOVEFILE_EXCEPTION($finalAbsolutePath);
        }
    } else {
        if (!@copy($temporaryPath, $finalAbsolutePath)) {
            throw new ISC_PRODUCT_IMAGE_IMPORT_CANTMOVEFILE_EXCEPTION($finalAbsolutePath);
        }
    }

    // check to see if the uploaded image exceeds our internal maximum image size: ISC_PRODUCT_IMAGE_MAXLONGEDGE
    if ($library->getWidth() > ISC_PRODUCT_IMAGE_MAXLONGEDGE || $library->getHeight() > ISC_PRODUCT_IMAGE_MAXLONGEDGE) {
        // if it is, resize it and overwrite the uploaded source image because we only want to store images to a maximum size of ISC_PRODUCT_IMAGE_MAXLONGEDGE x ISC_PRODUCT_IMAGE_MAXLONGEDGE
        $library->setFilePath($finalAbsolutePath);
        $library->loadImageFileToScratch();
        $library->resampleScratchToMaximumDimensions(ISC_PRODUCT_IMAGE_MAXLONGEDGE, ISC_PRODUCT_IMAGE_MAXLONGEDGE);
        $library->saveScratchToFile($finalAbsolutePath, self::getWriteOptionsForImageType($library->getImageType()));
    }

    if ($productId === false) {
        // do not assign product hash, id or save to database if $productId is false
        if ($generateImages) {
            // manually generate images since, normally, a call to saveToDatabase would do it
            $image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_TINY, true, false);
            $image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_THUMBNAIL, true, false);
            $image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_STANDARD, true, false);
            $image->getResizedFileDimensions(ISC_PRODUCT_IMAGE_SIZE_ZOOM, true, false);
        }

        return $image;
    }

    if ($hash) {
        $image->setProductHash($productId);
    } else {
        $image->setProductId($productId);
    }

    // ISC_PRODUCT_IMAGE_SOURCEFILEDOESNTEXIST_EXCEPTION should never really happen at this point with all the checks above so, if it does, let the exception go unhandled to bubble up to a fatal error
    $image->saveToDatabase($generateImages);

    return $image;
}

Below loads the image and then resize:

public function loadImageFileToScratch()
{
    $filePath = $this->getFilePath();
    $imageType = $this->getImageType();

    // Attempt to increase the memory limit before loading in the image, to ensure it'll fit in memory
    ISC_IMAGE_LIBRARY_FACTORY::setImageFileMemLimit($filePath);

    switch ($imageType) {
        case IMAGETYPE_GIF:
            $this->_scratchResource = @imagecreatefromgif($filePath);
            if ($this->getScratchResource()) {
                imagecolortransparent($this->getScratchResource());
            }
            break;

        case IMAGETYPE_PNG:
            $this->_scratchResource = @imagecreatefrompng($filePath);
            if ($this->_scratchResource) {
                // this sets up alpha transparency support when manipulating and saving the in-memory image
                imagealphablending($this->getScratchResource(), false);
                imagesavealpha($this->getScratchResource(), true);
            }
            break;

        case IMAGETYPE_JPEG:
            $this->_scratchResource = @imagecreatefromjpeg($filePath);
            break;

        default:
            throw new ISC_IMAGE_LIBRARY_GD_UNSUPPORTEDIMAGETYPE_EXCEPTION($imageType);
    }

    $this->_updateImageInformation(true);

    if (!$this->getScratchResource()) {
        throw new ISC_IMAGE_LIBRARY_GD_IMAGECREATEFROMFILE_EXCEPTION($imageType, $filePath);
    }
}

After loading the image and making the necessary changes, the script below saves:

    public function saveScratchToFile($destinationFilePath, ISC_IMAGE_WRITEOPTIONS $imageWriteOptions)
{
    $imageType = $imageWriteOptions->getImageType();

    switch ($imageType) {

        case IMAGETYPE_JPEG:
            imagejpeg($this->getScratchResource(), $destinationFilePath, (int)$imageWriteOptions->getQuality());
            break;

        case IMAGETYPE_PNG:
            if (version_compare(PHP_VERSION, '5.1.3', '>=')) {
                // filters parameter was added in 5.1.3
                imagepng($this->getScratchResource(), $destinationFilePath, (int)$imageWriteOptions->getCompression(), (int)$imageWriteOptions->getFilters());
            } else if (version_compare(PHP_VERSION, '5.1.2', '>=')) {
                // quality parameter was added in 5.1.2
                imagepng($this->getScratchResource(), $destinationFilePath, (int)$imageWriteOptions->getCompression());
            } else {
                imagepng($this->getScratchResource(), $destinationFilePath);
            }
            break;

        case IMAGETYPE_GIF:
            imagegif($this->getScratchResource(), $destinationFilePath);
            break;

        default:
            throw new ISC_IMAGE_LIBRARY_GD_UNSUPPORTEDIMAGETYPE_EXCEPTION($imageType);
            break;
    }

    isc_chmod($destinationFilePath, ISC_WRITEABLE_FILE_PERM);
}

"getCompression()" is 0, I tried to leave with 1, 5 and 9, all were the same.

"getFilters()" is like "PNG_ALL_FILTERS".

The PHP version is larger than 5.1.3, so it is using the first option.

I can’t post the whole script here as it is multiple files for the whole upload/resize process, many of them are not even part of this problem. But if anything is missing, just tell me that I look in the scripts and let me know how it is.

This problem is happening only with PNG. JPEG and GIF are forming clean and smooth images, both in "zoom", as in "pattern" and "thumbnail".

Are there any tricks to work with PNG in PHP? Something related to Alpha (I don’t work much with images, so I don’t know what might be going on).

Thank you.

  • 1

    Junior, just to be sure, is the serrated PNG image actually being resized and saved into a new dimension? Check the image properties and check the original dimensions to see if it is not being resized by HTML, I do not know the GD do this with the image, except if there is a bug in the compression function of your script, value "0" is without any compression, that is, quality, and 9 is total compression, and even so the PNG compression of GD changes very little the quality, unlike the 'quality' from 0 to 100 for JPG, which leaving "0" is horrible image.

  • 1

    @Thyagothysoft I hadn’t even touched it, but after you spoke I went to see the image, I even downloaded it and actually it was resized.

  • Junior, I hadn’t really come across this situation yet. From what I understood in the code, first is made the resize then transparency is applied. If this is indeed the case, the process with imagecolorallocatealpha in the original size applying transparency, and then performing resize? Maybe it will improve. I tested with white background and this serration effect is not caused.

  • 1

    Forget it, it’s not that, I just tested your image on my function and it didn’t serrate like yours. I’ll draw up an answer based on my role, it’ll be easier to adapt.

  • Okay, thank you very much.

2 answers

1

This answer performs what you need and with the quality you need, but you will need to perform a comparison and find where the problem is. It becomes more difficult to find the solution directly in your code because there are many external methods and you can’t know all the values that are being passed and how they are being passed.

Note: The script below is the basic to answer your question, there are no checks or anything exceptional, is functional to perform the test with the image of your question.

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="img" />
    <input type="submit" name="ok" value="Enviar" />
</form>

<?php
if($_POST['ok']) {
    $tempname = $_FILES["img"]["tmp_name"]; // Caminho completo da imagem original.
    $url = "img/nova_imagem.png"; // Caminho onde será salvo a nova imagem e nome do arquivo.
    $max_width = 300; // Largura final da imagem.
    $max_height = 225; // Altura final da imagem.

    move_uploaded_file($tempname, $url); // Move arquivo para servidor.

    // Pega a largura, altura, tipo e atributo da imagem
    list($image_width, $image_height, $type, $attribute) = getimagesize($url);

    // Testa se é preciso redimensionar a imagem
    if(($image_width > $max_width) || ($image_height > $max_height)) {

        if($image_width > $image_height) {
            $new_height = round(($max_width / $image_width) * $image_height);
            $new_width = $max_width;
        }

        if($image_height > $image_width) {
            $new_width = round(($max_height / $image_height) * $image_width);
            $new_height = $max_height;
        }

        if($image_width == $image_height) {
            if($max_width > $max_height) {
                $new_width = round($max_width - ($max_width - $max_height));
                $new_height = $max_height;
            }
            if($max_height > $max_width) {
                $new_height = round($max_height - ($max_height - $max_width));
                $new_width = $max_width;
            }

            if($max_height == $max_width) {
                $new_height = $max_height;
                $new_width = $max_width;
            }
        }

        // Cria uma nova imagem com o novo tamanho  
        $new_image = imagecreatetruecolor($new_width, $new_height);

        // Define o background desta nova imagem (RGB), neste caso, 'transparente' como precisa.
        $transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127);
        imagefill($new_image, 0, 0, $transparent);
        imagealphablending($new_image, true);
        imagesavealpha($new_image, true);

        $source = imagecreatefrompng($url);
                  imagecopyresampled($new_image, $source, 0, 0, 0, 0,   $new_width, $new_height, $image_width, $image_height);
                  imagepng($new_image, $url, 0); // Compactação '0'

        // Destrói as imagens criadas
        imagedestroy($new_image);
        imagedestroy($source);
    }
}
?>

Need to test with images of other dimensions, change the variables $max_width and $max_height to the desired value.

[UPDATE]

Final Result

Resultado Final

  • 1

    Thank you. I will try to track the step-by-step of this script, from upload to resize, to see where and how I can deploy this script you’ve passed.

0

Try passing the second parameter of imagealphablending as true:

imagealphablending($image, true);

Otherwise, try to create a transparent color and fill the image with this color, before making a copy of it:

$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefill($image, 0, 0, $transparent);
imagealphablending($image, true);

Vi in that Stack Overflow response.

  • Unfortunately not solved. Continues with the same problem.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.