Use LiipImagineBundle to Resize Image after Upload?
Asked Answered
R

5

22

I'm using the LiipImagineBundle with Symfony 2.1 and would like to resize user uploaded images upon upload before saving them to the permanent filesystem location (to strip metadata, impose jpeg format, and limit the size of the file). I have to call a 'strip' and 'resize' filter from the controller and then save the filtered image from a temporary location to a folder of my choosing in the filesystem.

I tried to use the LiipImageBundle Controller as a service as indicated in the bundle's readme but the called Action is mainly for creating a filtered image in the cache directory when a request is made to display the image (using it for filtering during uploading is another case). I tried to implement it as follows anyways, and got it to work. I had to first move the file from the web server's php temporary directory to a directory in the web folder to be able to apply the filter. Secondly, I applied the filter and deleted (unlink()) the initial unfiltered file. Lastly, I had to move (rename()) the filtered file to the permanent location in the filesystem. It was necessary to move the file twice, apply the filter once, and delete (unlink) 1 file to make it all work. Is there a better way (not requiring the intermediate move) to use the bundle at upload?

class MyController extends Controller
{
    public function new_imageAction(Request $request)
    {
        $uploadedFile = $request->files->get('file');
        $tmpFolderPathAbs = $this->get('kernel')->getRootDir() . '/../web/uploads/tmp/';
        $tmpImageNameNoExt = rand();
        $tmpImageName = $tmpImageNameNoExt . '.' . $fileExtension;
        $uploadedFile->move($tmpFolderPathAbs, $tmpImageName);
        $tmpImagePathRel = '/uploads/tmp/' . $tmpImageName;
        // Create the filtered image in a tmp folder:
        $this->container->get('liip_imagine.controller')->filterAction($request, $tmpImagePathRel, 'my_filter');
        unlink($tmpFolderPathAbs . $tmpImageName);
        $filteredImagePathAbs = $this->get('kernel')->getRootDir() . '/../web/uploads/cache/my_filter/uploads/tmp/' . $tmpImageNameNoExt . '.jpeg';
        $imagePath = $imageManagerResponse->headers->get('location');
        // define permanent location ($permanentImagePathAbs)...
        rename($filteredImagePathAbs, $permanentImagePathAbs);
    }
}

My filter in the app/config/config.yml is as follows:

liip_imagine:
    filter_sets:
        my_filter:
            format: jpeg
            filters:
                strip: ~
                thumbnail: { size: [1600, 1000], mode: inset }

A similar question was asked for the ImagineAvalancheBundle but no much details are given. Perhaps implementing another service from the here provided list is a better solution?

Roybn answered 24/2, 2013 at 0:18 Comment(3)
Did you ever find a better solution? I also want this. The filter works well, but performance isn't great when I have a lot of thumbnails, moving them from the cache to a known location at upload time speeds things up dramatically.Tobacconist
@PeterWooster : I did not find another solution, but the one described above seems to work fine (I kept it that way).Roybn
I'm working on a solution and will post here when it works.Tobacconist
T
19

So here's a way to create thumbnails at upload with LiipImagineBundle. The trick is to use some of their other services:

    /**
     * Write a thumbnail image using the LiipImagineBundle
     * 
     * @param Document $document an Entity that represents an image in the database
     * @param string $filter the Imagine filter to use
     */
    private function writeThumbnail($document, $filter) {
        $path = $document->getWebPath();                                // domain relative path to full sized image
        $tpath = $document->getRootDir().$document->getThumbPath();     // absolute path of saved thumbnail

        $container = $this->container;                                  // the DI container
        $dataManager = $container->get('liip_imagine.data.manager');    // the data manager service
        $filterManager = $container->get('liip_imagine.filter.manager');// the filter manager service

        $image = $dataManager->find($filter, $path);                    // find the image and determine its type
        $response = $filterManager->get($this->getRequest(), $filter, $image, $path); // run the filter 
        $thumb = $response->getContent();                               // get the image from the response

        $f = fopen($tpath, 'w');                                        // create thumbnail file
        fwrite($f, $thumb);                                             // write the thumbnail
        fclose($f);                                                     // close the file
    }

This could also be done by directly calling the Imagine library functions if you had no other reason to include the LiipImagineBundle. I'll probably look into that in the future, but this works for my case and performs very well.

Tobacconist answered 27/3, 2013 at 20:51 Comment(5)
Thank you for posting this answer. As soon as I have a little time to try it, I'll let you know how it goes!Roybn
+1: You identified the right services to apply the filter and save the transformed image to the desired destination. The complete answer to my question is a combination of my steps to save a temporary image to the filesystem and your steps to apply the filter and save the transformed image to the final destination (as my question is for applying the filter directly on the uploaded image to resize it before saving it to the filesystem). Your solution is for creating thumbnails and storing them from an image that is already in the filesystem.Roybn
Notice that it does not seem possible to apply the filter to an image that is not already in a folder somewhere in the web directory, unless special access privileges are set.Roybn
I haven't tried applying a filter to a stream that hasn't been saved somewhere. I don't know if it's possible. If you figure out how please add a comment to this answer.Tobacconist
Undefined method get on Liip\ImagineBundle\Filter\FilterManager. Instead, $filterManager->applyFilter($image, $filter); should be used as @Imran's answer.Yser
G
10

Modified version of @Peter Wooster, and made it more generic so if someone is using it without Image entity he/she can easily take benifet from it. I'm giving here two versions, one that can be kept in utility or non-controller class. And the other version is for controller classes. It's up to you now where you like :)

To use outside of controller e.g. keeping it in utility classes:

/**
 * Write a thumbnail image using the LiipImagineBundle
 * 
 * @param Document $fullSizeImgWebPath path where full size upload is stored e.g. uploads/attachments
 * @param string $thumbAbsPath full absolute path to attachment directory e.g. /var/www/project1/images/thumbs/
 * @param string $filter filter defined in config e.g. my_thumb
 * @param Object $diContainer Dependency Injection Object, if calling from controller just pass $this
 */
public function writeThumbnail($fullSizeImgWebPath, $thumbAbsPath, $filter, $diContainer) {
    $container = $diContainer; // the DI container, if keeping this function in controller just use $container = $this
    $dataManager = $container->get('liip_imagine.data.manager'); // the data manager service
    $filterManager = $container->get('liip_imagine.filter.manager'); // the filter manager service
    $image = $dataManager->find($filter, $fullSizeImgWebPath); // find the image and determine its type
    $response = $filterManager->applyFilter($image, $filter);

    $thumb = $response->getContent(); // get the image from the response

    $f = fopen($thumbAbsPath, 'w'); // create thumbnail file
    fwrite($f, $thumb); // write the thumbnail
    fclose($f); // close the file
}

To use in controller e.g. CommonController or any other controller:

/**
 * Write a thumbnail image using the LiipImagineBundle
 * 
 * @param Document $fullSizeImgWebPath path where full size upload is stored e.g. uploads/attachments
 * @param string $thumbAbsPath full absolute path to attachment directory e.g. /var/www/project1/images/thumbs/
 * @param string $filter filter defined in config e.g. my_thumb
 */
public function writeThumbnail($fullSizeImgWebPath, $thumbAbsPath, $filter) {
    $container = $this->container;
    $dataManager = $container->get('liip_imagine.data.manager'); // the data manager service
    $filterManager = $container->get('liip_imagine.filter.manager'); // the filter manager service
    $image = $dataManager->find($filter, $fullSizeImgWebPath); // find the image and determine its type
    $response = $filterManager->applyFilter($image, $filter);

    $thumb = $response->getContent(); // get the image from the response

    $f = fopen($thumbAbsPath, 'w'); // create thumbnail file
    fwrite($f, $thumb); // write the thumbnail
    fclose($f); // close the file
}
Ganges answered 1/7, 2014 at 13:4 Comment(2)
Im getting the follwoing error when trying your code on my bitnami wamp stack, The mime type of image uploads/files/img-5535_05052015_923_c2a1a.JPG must be image/xxx got inode/x-empty. Can you help me with it?Vanscoy
@Vanscoy try to upload some other images and with some other type as well, as the image mime type looks corrupted or having some other issues.Ganges
A
1

Instead of loading the file using liip data manager, create liip binary object from the uploaded file:

use Liip\ImagineBundle\Model\Binary;

then use the following code:

                // Generate a unique name for the file before saving it
                $fileName = md5(uniqid()) . '.' . $uploadedFile->guessExtension();

                $contents = file_get_contents($uploadedFile);

                $binary = new Binary(
                    $contents,
                    $uploadedFile->getMimeType(),
                    $uploadedFile->guessExtension()
                );

                $container = $this->container;
                $filterManager = $container->get('liip_imagine.filter.manager');    // the filter manager service
                $response = $filterManager->applyFilter($binary, 'my_thumb');

                $thumb = $response->getContent();                               // get the image from the response

                $f = fopen($webRootDir .'/images_dir/' . $fileName, 'w');                                        // create thumbnail file
                fwrite($f, $thumb);                                             // write the thumbnail
                fclose($f);                                                     // close the file
Arum answered 2/7, 2017 at 10:42 Comment(0)
R
0

Given that I did not find a better way, I kept the solution described in the question description. That solution does not seem the optimal one from a performance stand point (it requires moving the file twice, apply the filter once, and unlink 1 file), but it gets the job accomplished.

UPDATE:

I changed my code to use the services indicated in Peter Wooster's answer, as shown below (that solution is more optimal as the filtered image is saved directly to the final destination):

class MyController extends Controller
{
    public function new_imageAction(Request $request)
    {
        $uploadedFile = $request->files->get('file');
        // ...get file extension and do other validation...
        $tmpFolderPathAbs = $this->get('kernel')->getRootDir() . '/../web/uploads/tmp/'; // folder to store unfiltered temp file
        $tmpImageNameNoExt = rand();
        $tmpImageName = $tmpImageNameNoExt . '.' . $fileExtension;
        $uploadedFile->move($tmpFolderPathAbs, $tmpImageName);
        $tmpImagePathRel = '/uploads/tmp/' . $tmpImageName;
        // Create the filtered image:
        $processedImage = $this->container->get('liip_imagine.data.manager')->find('my_filter', $tmpImagePathRel);
        $filteredImage = $this->container->get('liip_imagine.filter.manager')->get($request, 'my_filter', $processedImage, $tmpImagePathRel)->getContent();
        unlink($tmpFolderPathAbs . $tmpImageName); // eliminate unfiltered temp file.
        $permanentFolderPath = $this->get('kernel')->getRootDir() . '/../web/uploads/path_to_folder/';
        $permanentImagePath = $permanentFolderPath . 'my_image.jpeg';
        $f = fopen($permanentImagePath, 'w');
        fwrite($f, $filteredImage); 
        fclose($f);
    }
}
Roybn answered 1/3, 2013 at 22:40 Comment(1)
you could write a service that does the whole thing for you and then use that instead of LiipImagine.Nebraska
L
0

I've written a bundle that solves exact this problem. While VichUploaderBundle provides easy uploading using ORM's Lifecycle callbacks, LiipImagine does a great job at resizing.

Here is the combination of it: https://github.com/RSSfeed/VichImagineBundle

See the short readme on how to implement it in only few minutes.

Linguistics answered 8/11, 2016 at 22:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.