How to store an Intervention image in gridfs/mongodb? - php

In my application there is a call which might request a resized version of the image. In the case when it is resized I want to store the resized version long-term onto gridfs, exactly like the other images.
I can easily resize the image and return it using Intervention (http://image.intervention.io/getting_started/introduction) however it seems to be that it's not possible to store the resized image to the DB without saving it to a temporary file.
In particular this fails:
$bucket = DB::getMongoDB()->selectGridFSBucket();
$stream = $bucket->openDownloadStreamByName($name);
$document = $bucket->getFileDocumentForStream($stream);
$metadata = $document['metadata']->getArrayCopy();
$file = stream_get_contents($stream);
$img = Image::cache(function($image) use ($file){
$image->make($file);
}, 60, true);
$img->resize($width, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
$bucket->uploadFromStream($randomName, $img, ['metadata' => $metadata]);
The call to uploadFromStream fails saying: Expected $source to have type "resource" but found "Intervention\Image\CachedImage".
Trying to change:
$bucket->uploadFromStream($randomName, $img, ['metadata' => $metadata]);
to:
$bucket->uploadFromStream($randomName, $img->stream('png'), ['metadata' => $metadata]);
Leads to the same error only that the type changes from Intevention\Image\CachedImage to GuzzleHttp\Psr7\Stream.
Now I could do:
$img->save('/tmp/test.png')
$stream = fopen('/tmp/test.png')
$bucket->uploadFromStream($randomName, $stream, ['metadata' => $metadata]);
but this is abysmally bad because:
it has a race condition. I would need to ensure the filename used is unique otherwise it might be overwritten
it writes to disk which means it's going to be way slower than just using an in-memory stream
it writes to disk which means it deteriorates more. Consider that this write is completely useless so I'd avoid using it. It can be triggered quite often.
So, is there a way to save the resized image to GridFS without passing throught the disk?

I found a solution: you can obtain the binary data as a string using $img->response()->content() and then instead of using uploadFromStream you can use openUploadStream and write the data.
The full working solution would be:
$bucket = DB::getMongoDB()->selectGridFSBucket();
$stream = $bucket->openDownloadStreamByName($name);
$document = $bucket->getFileDocumentForStream($stream);
$metadata = $document['metadata']->getArrayCopy();
$file = stream_get_contents($stream);
$img = Image::cache(function($image) use ($file){
$image->make($file);
}, 60, true);
$img->resize($width, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
$stream = $bucket->openUploadStream($randomName, ['metadata' => $metadata]);
fwrite($stream, $img->response()->content());
fclose($stream);

Related

Uploading to S3 with Laravel Intervention image returns boolean and not path

I am having an issue with uploading an image in Laravel. Sometimes the image needs reorienting - when doing this with Intervention image it simply returns a boolean instead of the path of the image in s3. When using it without Intervention Image it works fine
I've tried exif reading data and using imagerotate to no avail, it errors out
I run the following
$image = $request->file('photo');
$path = \Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image, 'public');
dd($path); // /users/1/posts/39grjigrje.jpg
the $path variable is great and is the s3 path, however running:
$image = $request->file('photo');
$image = \Image::make($image);
$image->orientate();
$path = \Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image, 'public');
dd($path); // true
the $path variable is simply just a boolean and doesn't return the stored file path
I expect a path e.g. /images/1/kfjeieuge.jpg which I get when i don't use intervention image, when I use intervention I get a boolean.
I too had faced the same issue a few years back. I solved that by using following steps:
1.You may have to encode the image after an Intervention operation on it.
To achieve encoding refer this documentation on stream method:Create encoded image stream
Encodes the current image in given format and given image quality and creates new PSR-7 stream based on image data.
$image = Image::make(request()->file('image'))->orientate()->encode('jpg');
2.And then storing it with a stream
$path = Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image->stream(),'public');
This should achieve your objective.
In first example:
$image = $request->file('photo');
$path = \Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image,
'public');
$request->file('photo') file will return instance of Illuminate\Http\UploadedFile class. you can check it here in documentation link.
And as Tarun said put method check for instance of Illuminate\Http\UploadedFile class. So in that case, it will uploaded successful.
In second example:
$image = $request->file('photo');
$image = \Image::make($image); //here
$image->orientate();
$path = \Storage::disk('s3')->put('users/' . \Auth::id() . '/posts', $image, 'public');
You are overwriting $image variable with instance Image class (second line). Which is wrong. You need to pass Stream or file.
Try below code:
$image = $request->file('photo');
$image = \Image::make($image); //here
$image->orientate()->encode('jpg');
$filename = time() . '.jpg';
$path = \Storage::disk('s3')->put('users/' . \Auth::id() . '/posts/'.$filename, $image->getEncoded(), 'public');
You can solve it as described in the below link
https://laracasts.com/discuss/channels/laravel/saving-an-intervention-image-instance-into-amazon-s3
$path = \Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image->stream()->__toString(), 'public');
Update: 5 Jul
If you look at the source code it is supposed to return bool only
https://github.com/laravel/framework/blob/c7bdf66062af3c63648f7c29591476e6db92c885/src/Illuminate/Filesystem/FilesystemAdapter.php#L192
/**
* Write the contents of a file.
*
* #param string $path
* #param string|resource $contents
* #param mixed $options
* #return bool
*/
public function put($path, $contents, $options = [])
{
$options = is_string($options)
? ['visibility' => $options]
: (array) $options;
// If the given contents is actually a file or uploaded file instance than we will
// automatically store the file using a stream. This provides a convenient path
// for the developer to store streams without managing them manually in code.
if ($contents instanceof File ||
$contents instanceof UploadedFile) {
return $this->putFile($path, $contents, $options);
}
return is_resource($contents)
? $this->driver->putStream($path, $contents, $options)
: $this->driver->put($path, $contents, $options);
}
And as you can see a Stream and file is handled differently
And the ideal thing is to use url function to get the url
Storage::disk('s3')->url($filename)
Try this
$image = \Image::make($request->file('photo')->getRealpath());
$image->orientate();
$image->encode('jpg');
OR
$image->response('jpg', 100);
$path = \Storage::disk('s3')->put('users/'.\Auth::id().'/posts', $image, 'public');

My php Watermark function is not working for png image

I am using a PHP function to add my logo as the watermark on images uploaded on my website. But I don't know why my watermark function is not working for png files. however, it works for jpeg files perfectly. this is my PHP function.
function watermark($img) {
global $wm_file, $wm_right, $wm_bottom;
// image values pulled from config.inc.php
$logo = './images/' . $wm_file; // path to the watermark.png
$sp = $wm_right; // spacing from right side
$sq = $wm_bottom; // spacing from bottom
$size = getImageSize($img);
$sizel = getImageSize($logo);
$imgA = imageCreateFromJpeg($img);
imageAlphaBlending($imgA, TRUE);
if($sizel[0] > $size[0] || $sizel[1] > $size[1])
{
// logo size > img size
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
$sizel[0] = ($sizel[0]/2);
$sizel[1] = ($sizel[1]/2);
}
else
{
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
}
$imgBa = imageCreateFromPng($logo);
$imgB = imageCreateTrueColor($sizel[0], $sizel[1]);
imageAlphaBlending($imgB, TRUE);
imageCopyResampled($imgB, $imgBa, 0, 0, 0, 0, $sizel[0], $sizel[1], $sizelo[0], $sizelo[1]);
imageColorTransparent($imgB, ImageColorAllocate($imgB, 0, 0, 0));
$perc = 100;
imageCopymerge($imgA, $imgB, ($size[0]-$sizel[0]-$sp), ($size[1]-$sizel[1]-$sq), 0, 0, $sizel[0], $sizel[1], $perc);
unlink($img);
if(imageJpeg($imgA, $img, 100))
{
imageDestroy($imgB);
imageDestroy($imgA);
return true;
}
chmod($img, 0777);
}
The problem I see is that you are using imageCreateFromJpeg() as the way to generate the resource for your $img that you are passing to the function.
If you pass a jpeg through the function it will work. If you pass a png it will not.
I recommend using imagecreatefromstring() to create all your resources as it is not dependent on the file type. Like so:
$source = imagecreatefromstring(file_get_contents($filePath));
Another benefit of this is that it will return false if the function fails to create a resource from the file path that you supplied meaning that the file is not an image file.
Now that you have a resource to use for the rest of your code, imageJpeg() will save the resource as a jpeg back to the file path.
Hope that helps.
One other side note. If you intend on using bmp images, the GD library does not have a built in function for bmps. However on PHP.net, someone did write a createimagefromBMP() that works really well. Also I think that on the latest version of PHP the GD library does now actually have a createimagefromBMP() function.
I also see that you are using unlink() to delete the image from your directory. This is not necessary for two reasons. The imageJpeg() will just overwrite the original. Also, if for some reason your script fails it may delete the image prematurely and you will loose the image without the new one being written.
Please be careful when using chmod(), always make sure that you set permissions back to the original permissions when you are done.
chmod($img, 777); //Give broad permissions.
//Do something.
chmod($img, 600(or whatever they were)); //Reset permission back to where they were before you changed them.

php laravel What is the Stream() function in image intervention

I wonder what the stream() function in image intervention does? http://image.intervention.io/api/stream
Right now I am uploading my images to amazon S3 like this:
public function uploadLargeAndMainImages($file,$adId)
{
$s3 = Storage::disk('s3');
$extension = $file->guessExtension();
$filename = uniqid() . '.' . $extension;
//Create and resize images
$image = Image::make($file)->resize(null, 600, function ($constraint) {
$constraint->aspectRatio();
});
$image->encode($extension);
$imageLarge = Image::make($file)->resize(null, 800, function ($constraint) {
$constraint->aspectRatio();
});
$imageLarge->encode($extension);
// upload image to S3
$s3->put("images/{$adId}/main/" . $filename, (string) $image, 'public');
$s3->put("images/{$adId}/large/" . $filename, (string) $imageLarge, 'public');
// make image entry to DB
$file = File::create([
'a_f_id' => $adId,
'file_name' => $filename,
]);
}
Its all written in the Intervention Docs you've mentioned above:
The stream() method encodes the image in given format and given
image quality and creates new PSR-7 stream based on image data.
It returns a PSR-7 stream as instance of GuzzleHttp\Psr7\Stream.
// encode png image as jpg stream
$stream = Image::make('public/foo.png')->stream('jpg', 60);
Just for the sake of demonstration you can use the stream() method with S3 like this:
...
$image_normal = Image::make($image)->widen(800, function ($constraint) {
$constraint->upsize();
});
$image_thumb = Image::make($image)->crop(100,100);
$image_normal = $image_normal->stream();
$image_thumb = $image_thumb->stream();
Storage::disk('s3')->put($path.$file, $image_normal->__toString());
Storage::disk('s3')->put($path.'thumbnails/'.$file, $image_thumb->__toString());
It think you get it!

How to upload image to AWS S3 in PHP from memory?

So I currently have an upload system working using AWS S3 to upload images.
Here's the code:
//Upload image to S3
$s3 = Aws\S3\S3Client::factory(array('key' => /*mykey*/, 'secret' => /*myskey*/,));
try {
$s3->putObject(array(
'Bucket' => "bucketname",
'Key' => $file_name,
'Body' => fopen(/*filelocation*/, 'r+')
));
} catch(Exception $e) {
//Error
}
This image can be a jpeg or png, and I want to convert it to a png before uploading. To do this I use:
//This is simplified, please don't warn about transparency, etc.
$image = imagecreatetruecolor($width, $height);
imagecopyresampled($image, $source, 0, 0, 0, 0, etc.);
So I have this $image object in memory.
I want to upload this to S3 without having to save it locally, upload it and then delete it locally; this extra step seems pointless. But I can't work out how to upload this $image object directly.
Any ideas how this would be done? I'd assumed fopen() would create an object of a similar type to imagecreatetruecolor(), but I've tried passing the $image object in and it doesn't work - whereas it does if I open an image locally with fopen().
You can capture the content of a GD image resource using output buffering:
ob_start();
imagepng($image);
$pngdata = ob_get_clean();

Resizing base64 Images

I have multiple Images - saved as Base64 Strings and now i want to resize these images to get thumbnails of them...
Best would be using Javascript (Node-Server) to resize them, but it would be possible to resize them with php, too.
Thanks in advance
I agree to the method from Jon Hanna: Do Parsing the Base64code then load it to GD Image before Resample. However to get it back as data it is not as easy as I though. On php in GAE it will need to enable output buffering by setting output_buffering = "On" in php.ini file.
Here I explain the step in detail.
This doc is taken as reference to Create Image Resource using the Parsing of Base64code: http://php.net/manual/en/function.imagecreatefromstring.php
// Create image resource from Base64code
$data64 = 'iVBORw0KGgoAAAANSUhEUgAAABwAAAASCAMAAAB/2U7WAAAABl'
. 'BMVEUAAAD///+l2Z/dAAAASUlEQVR4XqWQUQoAIAxC2/0vXZDr'
. 'EX4IJTRkb7lobNUStXsB0jIXIAMSsQnWlsV+wULF4Avk9fLq2r'
. '8a5HSE35Q3eO2XP1A1wQkZSgETvDtKdQAAAABJRU5ErkJggg==';
$image = imagecreatefromstring(base64_decode($data64));
This is an image resource which can be directly put to the Resample function: http://php.net/manual/en/function.imagecopyresampled.php
// Resample
$image_p = imagecreatetruecolor($new_w, $new_h);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_w, $new_h, $org_w, $org_h);
The result is also an image resource. To get it as a data we need Buffering.
See
how to create a base64encoded string from image resource
// Buffering
ob_start();
imagepng($image_p);
$data = ob_get_contents();
ob_end_clean();
Using doc below I set a GCS bucket on my project as a website so I can Store & Display it directly:
https://cloud.google.com/storage/docs/website-configuration#tips
//Store & Display
$context = stream_context_create([
'gs' =>[
'acl'=> 'public-read',
'Content-Type' => 'image/jpeg',
'enable_cache' => true,
'enable_optimistic_cache' => true,
'read_cache_expiry_seconds' => 300,
]
]);
file_put_contents("gs://mybucket/resample/image.jpeg", $data, false, $context);
header("Location: http://mybucket/resample/image.jpeg");
Your best bet is to use PHPThumb in PHP.
An alternative is to invoke ImageMagick however you prefer:
http://coffeeshopped.com/2009/01/creating-image-thumbnails-using-php-and-imagemagick
http://www.hacksparrow.com/node-js-image-processing-and-manipulation.html
No idea how to do that (or well, anything) in node.js, but the PHP bit of your question is certainly possible. After parsing the Base64, load it into a GD image and then resample it.
http://php.net/manual/en/function.imagecopyresampled.php
Maybe you just can use a lib to handle that. Try WideImage. I have used it and worked nicely.
Example:
$image = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $req->image));
$thumbnail = WideImage::load($image)
->resize(300, 300, 'inside')
->crop('center', 'center', 300, 300);
Library Documentation: http://wideimage.sourceforge.net/

Categories